Design a test/live switch and a page that says what is missing
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>feature/betriebsmodus
parent
36cb684d8b
commit
3768433792
|
|
@ -0,0 +1,382 @@
|
|||
# Spec — Betriebsmodus (Test/Live) und Betriebsbereitschaft
|
||||
|
||||
**Datum:** 2026-07-30
|
||||
**Status:** entworfen, noch nicht umgesetzt
|
||||
**Vorgänger-Kontext:** `docs/handoffs/2026-07-30-real-run-handoff.md` §4
|
||||
|
||||
---
|
||||
|
||||
## 1. Ziel
|
||||
|
||||
Zwei Dinge, die zusammengehören:
|
||||
|
||||
**A — Betriebsmodus.** Ein Schalter in der Konsole, `test` oder `live`. Jedes
|
||||
Zugangsdatum im Tresor bekommt zwei Plätze; der aktive Modus entscheidet,
|
||||
welcher gilt. Ist der Testplatz leer, gilt der Live-Platz — mit **einer**
|
||||
Ausnahme, siehe §4.
|
||||
|
||||
**B — Betriebsbereitschaft.** Eine Seite, die jedes Pflichtfeld dieser
|
||||
Installation aufführt, seinen Zustand zeigt und sagt, **was kaputtgeht, wenn
|
||||
es fehlt**. Der Zweck ist genau einer: den Liveserver vollständig befüllen zu
|
||||
können, bevor der erste Host onboardet wird — statt es nach der Zahlung eines
|
||||
Kunden zu erfahren.
|
||||
|
||||
### Nicht Teil dieses Vorhabens
|
||||
|
||||
- **Testdaten-Trennung** (eigene Belegserie, Markierung, Ausschluss aus dem
|
||||
Umsatz, Aufräumen inkl. VM und DNS). Eigener Entwurf, hängt an diesem hier.
|
||||
Beschlossen, aber getrennt — dort wird auf einem Host eine VM zerstört, und
|
||||
das braucht eigene Sorgfalt. Siehe §9.
|
||||
- Zwei Plätze für die **einfachen Einstellungen** (DNS-Zone, WireGuard-Endpunkt,
|
||||
Traefik-Pfad). Das sind keine Zugangsdaten, und eine getrennte Testzone wurde
|
||||
bewusst nicht gewählt.
|
||||
- Blöcke A–D aus dem Handoff (goldene Vorlage, `vmbr0`, Traefik als Systemdienst).
|
||||
|
||||
---
|
||||
|
||||
## 2. Ausgangsbefund — gemessen, nicht gelesen
|
||||
|
||||
Alles hier am 2026-07-30 auf dem Entwicklungsserver nachgemessen.
|
||||
|
||||
| Befund | Beleg |
|
||||
|---|---|
|
||||
| Der Tresor hat **einen** Platz je Zugangsdatum | `SecretVault::REGISTRY`, fünf Einträge, je ein `config`-Schlüssel |
|
||||
| Der hinterlegte Stripe-Schlüssel ist ein **Testschlüssel** | Präfix `sk_test_` |
|
||||
| `STRIPE_WEBHOOK_SECRET` liegt bewusst **nicht** im Tresor | `SecretVault` Kopfkommentar; `StripeWebhookController:23` liest `config()` |
|
||||
| Es gibt **keine** Bereitschaftsprüfung | Einziges Vorbild: `CompanyProfile::missingForInvoicing()`, benutzt in `Finance` und `NewInvoice` |
|
||||
| `mail.default` steht auf `log` | Zugangsdaten-Mail landet in der Datei, nicht beim Kunden |
|
||||
| Zwei Hosts stehen `active` **ohne** `api_token_ref` | `public_ip` `203.0.113.11`/`.21` — RFC 5737, Dokumentationsbereich |
|
||||
| `CLUPILOT_WG_ENDPOINT` war eine private Adresse | `10.10.90.185:51820` |
|
||||
| Kein Worker meldet, dass er lebt | Weder Zeitplaner noch `queue-provisioning` hinterlassen eine Spur |
|
||||
|
||||
Die letzten vier sind der Grund für diese Spec. Jeder davon ist in der Konsole
|
||||
heute **unsichtbar**, und jeder bricht die Kette an einer anderen Stelle —
|
||||
zwei davon erst nach der Zahlung.
|
||||
|
||||
---
|
||||
|
||||
## 3. Der Schalter
|
||||
|
||||
**Speicherort:** `App\Support\Settings`, Schlüssel `platform.mode`, Werte `test`
|
||||
oder `live`.
|
||||
|
||||
Nicht die `.env`. `Settings` ist zwischengespeichert und verwirft den
|
||||
Zwischenspeicher beim Schreiben — ein Umlegen wirkt sofort, auch in den
|
||||
Warteschlangen-Prozessen, die seit Stunden laufen. Eine `.env`-Variable bräuchte
|
||||
einen Containerneustart; genau diese Falle hat am 2026-07-30 die korrigierte
|
||||
DNS-Zone acht Stunden lang wirkungslos gelassen.
|
||||
|
||||
**Gelesen über** `App\Support\OperatingMode::current(): Mode`, damit nirgends
|
||||
sonst im Code ein String verglichen wird. `Mode` ist ein Enum mit `Test` und
|
||||
`Live`.
|
||||
|
||||
**Vorgabe:** `live`. Eine Installation, die nichts gespeichert hat, soll nicht
|
||||
in einem Modus stehen, der stillschweigend andere Zugangsdaten benutzt.
|
||||
|
||||
**Wer darf umlegen:** `secrets.manage` (Inhaber) **und** bestätigtes Passwort —
|
||||
dieselbe Sperre wie der Tresor, weil der Wechsel auf Live der Moment ist, ab dem
|
||||
echtes Geld fließt. Bestätigung im Modal nach **R23**, nie `wire:confirm`.
|
||||
|
||||
**Wo:** Integrationsseite, Reiter `services`, **über** allen Zugangsdaten — er
|
||||
regiert sie. Zusätzlich eine Plakette im Kopf der Konsole (`layouts.admin`),
|
||||
solange der Modus `test` ist. „Bin ich im Test?" darf keine Frage sein, die man
|
||||
durch Navigieren beantwortet.
|
||||
|
||||
---
|
||||
|
||||
## 4. Zwei Plätze je Zugangsdatum
|
||||
|
||||
Die kuratierte Liste bleibt, wie sie ist — der Tresor bleibt ein Tresor und kein
|
||||
Schlüssel/Wert-Speicher. Nur die **Zeile** wird zweigeteilt:
|
||||
|
||||
```
|
||||
stripe.secret:test stripe.secret:live
|
||||
dns.token:test dns.token:live
|
||||
monitoring.token:test monitoring.token:live
|
||||
inbound_mail.password:test inbound_mail.password:live
|
||||
ssh.private_key:test ssh.private_key:live
|
||||
```
|
||||
|
||||
### Auflösungsregel
|
||||
|
||||
`SecretVault::get($key)` löst in dieser Reihenfolge auf:
|
||||
|
||||
1. Zeile des **aktiven Modus**
|
||||
2. leer **und** der Eintrag erlaubt Rückfall → Zeile `live`
|
||||
3. immer noch leer → `config()`, also `.env` — wie heute
|
||||
|
||||
Schritt 2 ist die Regel des Inhabers: für DNS, Monitoring, Postfach und SSH gibt
|
||||
es nur ein Konto, und zwei Felder mit zwingend gleichem Inhalt wären eine
|
||||
Attrappe.
|
||||
|
||||
Schritt 3 bleibt **einwertig und modusfrei**. Die `.env` ist der Weg für eine
|
||||
Installation, in der noch gar nichts gespeichert ist; ein zweiter Satz
|
||||
Variablen dort würde den Anfangszustand verdoppeln, den es nur einmal gibt.
|
||||
|
||||
### Stripe ist ausgenommen — in beide Richtungen
|
||||
|
||||
Ein Merkmal `'strict' => true` in `REGISTRY`, das **nur** `stripe.secret` trägt.
|
||||
|
||||
Bedeutung: kein Rückfall, weder von Test auf Live noch umgekehrt. Fehlt der
|
||||
Schlüssel des aktiven Modus, liefert `get()` `null`.
|
||||
|
||||
Ohne diese Ausnahme führte die Rückfallregel dazu, dass ein Testkauf bei
|
||||
fehlendem Testschlüssel still den **Live-Schlüssel** benutzt und **echtes Geld**
|
||||
abbucht, während die Konsole „Testbetrieb aktiv" anzeigt. Das ist genau die
|
||||
Klasse von stillem Fehler, gegen die der ganze erste Durchlauf gebaut ist.
|
||||
|
||||
**Folge für die Aufrufer:** `HttpStripeClient::secret()` wirft eine sprechende
|
||||
Ausnahme statt einen Aufruf ohne Token abzusetzen. Die Kasse fragt vorher die
|
||||
Bereitschaftsprüfung und weist die Bestellung mit lesbarem Text ab (§6).
|
||||
|
||||
### Der Webhook-Schlüssel
|
||||
|
||||
Bleibt in der `.env`. Seine Begründung gilt unverändert: er wird bei **jedem**
|
||||
eingehenden Zahlungsereignis gelesen, und ein Datenbankproblem würde die
|
||||
Signaturprüfung still fehlschlagen lassen.
|
||||
|
||||
Zwei Variablen, ausgewählt nach Modus:
|
||||
|
||||
| Modus | Variable |
|
||||
|---|---|
|
||||
| `live` | `STRIPE_WEBHOOK_SECRET` |
|
||||
| `test` | `STRIPE_WEBHOOK_SECRET_TEST` |
|
||||
|
||||
**Der Sonderfall ist bedacht.** Die Auswahl liest den Modus aus der Datenbank —
|
||||
also aus genau der Quelle, die hier eigentlich vermieden werden sollte. Ist sie
|
||||
nicht erreichbar, liefert `Settings::get()` seinen Vorgabewert `live` und
|
||||
schreibt eine Warnung. Die Auswahl fällt damit auf den **Live**-Schlüssel, und
|
||||
das ist die sichere Richtung: ein echtes Zahlungsereignis wird weiter korrekt
|
||||
geprüft, ein Testereignis scheitert laut. Der umgekehrte Vorgabewert wäre der
|
||||
gefährliche.
|
||||
|
||||
### Migration — sie rät nicht
|
||||
|
||||
- Jede bestehende Tresor-Zeile wandert auf `:live`.
|
||||
- **Ausnahme `stripe.secret`:** einsortiert am Präfix. `sk_test_` oder
|
||||
`rk_test_` → `:test`, alles andere → `:live`.
|
||||
- **Der Anfangsmodus folgt daraus**, statt aus der Vorgabe: landet der
|
||||
vorhandene Stripe-Schlüssel im Testplatz, war die Installation erkennbar im
|
||||
Testbetrieb, und `platform.mode` wird auf `test` gesetzt. Ist gar kein
|
||||
Stripe-Schlüssel da, bleibt es bei `live`.
|
||||
|
||||
Auf dem Entwicklungsserver heißt das: der laufende Aufbau bleibt heil, ohne dass
|
||||
jemand von Hand nachsortiert.
|
||||
|
||||
---
|
||||
|
||||
## 5. Betriebsbereitschaft
|
||||
|
||||
`App\Support\Readiness` — nach dem Muster von `CompanyProfile::missingForInvoicing()`,
|
||||
nur über alle Bereiche und mit mehr als einem Feldnamen je Eintrag.
|
||||
|
||||
Jede Prüfung liefert: Schlüssel, Beschriftung, **Gruppe** (was sie blockiert),
|
||||
Schweregrad (`blocking` oder `warning`), einen Satz dazu, was kaputtgeht, und
|
||||
den Reiter, der sie behebt.
|
||||
|
||||
Gruppiert nach dem, was sie blockiert — **nicht** danach, wo der Wert
|
||||
gespeichert ist. Ein Betreiber, der eine Installation befüllt, denkt in „was
|
||||
kann ich noch nicht", nicht in „liegt das im Tresor oder in den Einstellungen".
|
||||
|
||||
### 5.1 Abrechnung
|
||||
|
||||
| Prüfung | Grad | Bricht |
|
||||
|---|---|---|
|
||||
| Stripe-Schlüssel des aktiven Modus | blocking | Kein Auftrag entsteht |
|
||||
| Webhook-Schlüssel des aktiven Modus | blocking | Zahlung wird nie verbucht |
|
||||
| Firmendaten (`missingForInvoicing()` aufrufen, nicht verdoppeln) | blocking | `IssueInvoice` verweigert, Kunde bekommt keinen Beleg |
|
||||
| Rechnungsserie je Belegart vorhanden | blocking | Beleg kann keine Nummer ziehen |
|
||||
| Jede veröffentlichte Paketversion hat beide Stripe-Preise | blocking | Geprüfter EU-Firmenkunde kann nicht bestellen |
|
||||
|
||||
Der **Steuersatz** ist bewusst keine Prüfung: `CompanyProfile::taxRate()` hat
|
||||
einen `.env`-Rückfall und ist nie leer. Die **Einrichtungsgebühr** ebenso nicht:
|
||||
`0` ist ein gültiger Zustand, bei dem der Satz von der Preisseite verschwindet.
|
||||
|
||||
### 5.2 Host-Onboarding
|
||||
|
||||
| Prüfung | Grad | Bricht |
|
||||
|---|---|---|
|
||||
| `ssh.private_key` | blocking | `EstablishSshTrust`, phpseclib-Parserfehler |
|
||||
| `SECRETS_KEY` | blocking | Jeder Proxmox-Token unlesbar, keine Bereitstellung |
|
||||
| `VPN_CONFIG_KEY` | blocking | WireGuard-Konfiguration nicht lesbar |
|
||||
| `CLUPILOT_WG_HUB_PUBKEY`, `_ENDPOINT`, `_SUBNET` | blocking | Onboarding scheitert am Handshake |
|
||||
| Mindestens ein Rechenzentrum | blocking | `ValidateHostInput` kommt nicht durch |
|
||||
|
||||
### 5.3 Bereitstellung
|
||||
|
||||
| Prüfung | Grad | Bricht |
|
||||
|---|---|---|
|
||||
| `dns.token` | blocking | A-Record entsteht nicht, Kunde hat keine Adresse |
|
||||
| DNS-Zone gesetzt | blocking | Name wird in der falschen Zone gesucht |
|
||||
| Traefik-Pfad, dnsmasq-Verzeichnis | warning | Vorgabe greift, aber ungeprüft |
|
||||
| **Mindestens ein aktiver Host mit lesbarem `api_token_ref`** | blocking | Auftrag reserviert einen Host, den es nicht gibt — **nach** der Zahlung |
|
||||
| Jede veröffentlichte Version zeigt auf eine Vorlage, die auf mindestens einem aktiven Host existiert | blocking | `CloneVirtualMachine` stirbt nach der Zahlung |
|
||||
| `monitoring.token` | warning | Überwachung fehlt, Bereitstellung läuft |
|
||||
|
||||
Die vierte Zeile fängt genau die zwei Phantom-Hosts aus §2: `active`, aber ohne
|
||||
Token. Sie sind heute in der Konsole von einem echten Host nicht zu
|
||||
unterscheiden.
|
||||
|
||||
### 5.4 Zustellung
|
||||
|
||||
| Prüfung | Grad | Bricht |
|
||||
|---|---|---|
|
||||
| Mailversand geht **nicht** an `log` | blocking | VM läuft, Kunde erfährt nie davon |
|
||||
| Mindestens ein Postfach | blocking | Kein Absender |
|
||||
| Mail-Vorlagen vorhanden | warning | Fallback-Text statt gestalteter Mail |
|
||||
| `inbound_mail.password` | warning | Nur eingehende Post betroffen |
|
||||
|
||||
### 5.5 Betrieb — Herzschläge
|
||||
|
||||
Zwei Prüfungen, die es heute nirgends gibt und die neu entstehen müssen:
|
||||
|
||||
- **Zeitplaner:** eine Aufgabe je Minute schreibt `Settings`-Schlüssel
|
||||
`heartbeat.scheduler` mit Zeitstempel.
|
||||
- **Bereitstellungs-Worker:** der Zeitplaner stellt einen winzigen Auftrag in
|
||||
die `provisioning`-Warteschlange, der `Settings`-Schlüssel
|
||||
`heartbeat.queue_provisioning` schreibt. Das beweist den **Worker**, nicht nur
|
||||
den Zeitplaner — die beiden fallen getrennt aus.
|
||||
|
||||
Beide in `Settings`, nicht im Zwischenspeicher: ein Herzschlag, der mit dem
|
||||
Redis-Neustart verschwindet, meldet einen Ausfall, den es nicht gab.
|
||||
|
||||
Die Seite meldet einen Herzschlag als veraltet, wenn er älter als fünf Minuten
|
||||
ist. Damit wird „ohne Worker passiert schlicht nichts, ohne Fehlermeldung"
|
||||
sichtbar.
|
||||
|
||||
### 5.6 Prüfungen mit Knopf
|
||||
|
||||
Drei Einträge sind keine Feldabfragen, sondern echte Prüfungen — nach dem
|
||||
Vorbild von `StripeCheck`, das schon existiert und in `REGISTRY` als `check`
|
||||
eingetragen ist:
|
||||
|
||||
- **`DnsTokenCheck`** — fragt die Hetzner-API, ob der Token die konfigurierte
|
||||
Zone sieht **und beschreiben darf**. Ein Leserecht-Token sieht in der Konsole
|
||||
identisch aus wie ein Schreibrecht-Token; das ist der Unterschied zwischen
|
||||
„gesetzt" und „funktioniert".
|
||||
- **`WireguardEndpointCheck`** — ein UDP-Port lässt sich von außen nicht sauber
|
||||
anklopfen, aber ob dort eine private Adresse (RFC 1918) oder eine
|
||||
Dokumentationsadresse (RFC 5737) steht, ist entscheidbar. Fängt
|
||||
`10.10.90.185:51820`.
|
||||
- **`VmTemplateCheck`** — dieselbe Frage, die `VerifyVmTemplate` stellt, nur vor
|
||||
dem Kauf statt danach.
|
||||
|
||||
### 5.7 Darstellung
|
||||
|
||||
Eigene Seite `App\Livewire\Admin\Readiness`, Route `/admin/readiness`, in der
|
||||
Navigation. Auf der Übersicht ein Hinweis, sobald etwas Blockierendes offen ist.
|
||||
|
||||
Oben die eine Zeile, auf die es ankommt: **Bereit für Testbetrieb** bzw.
|
||||
**Bereit für Livebetrieb** — welche Schlüssel Pflicht sind, hängt am Modus.
|
||||
|
||||
Jeder Eintrag verlinkt auf den Reiter, der ihn behebt. Icons nach **R18**
|
||||
(`size-4` in Tabellen, einzeilig).
|
||||
|
||||
### 5.8 Die Seite ist kein Tor
|
||||
|
||||
Sie berichtet. Die harten Sperren bleiben dort, wo sie hingehören und heute
|
||||
schon stehen:
|
||||
|
||||
- `IssueInvoice` verweigert bei unvollständigen Firmendaten
|
||||
- `VerifyVmTemplate` verweigert einen Host ohne Vorlage
|
||||
|
||||
Dazu kommt **genau eine neue**: die Kasse nimmt keine Bestellung an, wenn dem
|
||||
aktiven Modus der Stripe-Schlüssel fehlt.
|
||||
|
||||
Eine Bereitschaftsseite, die selbst sperrt, wäre eine zweite Wahrheit neben den
|
||||
Prüfungen im Ablauf — und zwei Quellen für eine Frage sind der Weg, auf dem sie
|
||||
auseinanderlaufen.
|
||||
|
||||
---
|
||||
|
||||
## 6. Betroffene Stellen
|
||||
|
||||
| Datei | Änderung |
|
||||
|---|---|
|
||||
| `app/Support/OperatingMode.php` | **neu** — Enum + `current()` |
|
||||
| `app/Support/Readiness.php` | **neu** — die Prüfungsliste |
|
||||
| `app/Services/Secrets/SecretVault.php` | Zeilenschlüssel je Modus, `strict`-Merkmal, Auflösungsregel |
|
||||
| `app/Services/Stripe/HttpStripeClient.php` | `secret()` wirft bei fehlendem Schlüssel des aktiven Modus |
|
||||
| `app/Http/Controllers/StripeWebhookController.php` | Webhook-Schlüssel nach Modus |
|
||||
| `app/Livewire/Admin/Integrations.php` | Umschalter, zwei Felder je Eintrag |
|
||||
| `app/Livewire/Admin/Readiness.php` | **neu** |
|
||||
| `app/Livewire/Admin/ConfirmSwitchMode.php` | **neu** — Bestätigung nach R23 |
|
||||
| `app/Livewire/Admin/Overview.php` | Hinweis bei blockierenden Lücken |
|
||||
| `app/Services/Dns/DnsTokenCheck.php` | **neu** |
|
||||
| `app/Services/Vpn/WireguardEndpointCheck.php` | **neu** |
|
||||
| `app/Services/Proxmox/VmTemplateCheck.php` | **neu** |
|
||||
| `routes/`, `layouts.admin` | Route, Navigationseintrag, Modus-Plakette |
|
||||
| Migration | Zeilenschlüssel umstellen, Stripe am Präfix einsortieren, Anfangsmodus setzen |
|
||||
| Zeitplaner | zwei Herzschläge |
|
||||
|
||||
---
|
||||
|
||||
## 7. Tests
|
||||
|
||||
Nach dem Muster, das dieses Repo für seine Regeln benutzt (`IconLayoutTest`,
|
||||
`ConfirmInModalTest`, `DisplayTimezoneTest`): die Regel wird **per Test
|
||||
erzwungen**, nicht per Merksatz.
|
||||
|
||||
1. **Je Prüfung ein Test in beide Richtungen** — meldet fehlend, wenn es fehlt;
|
||||
meldet erfüllt, wenn es da ist.
|
||||
2. **Jeder `REGISTRY`-Eintrag muss auf der Bereitschaftsseite auftauchen.**
|
||||
Damit kann ein künftig hinzugefügtes Zugangsdatum nicht still an der
|
||||
Übersicht vorbeigehen.
|
||||
3. **Rückfall Test → Live** greift für die nicht-strikten Einträge.
|
||||
4. **Stripe fällt nicht zurück**, in beide Richtungen geprüft — der wichtigste
|
||||
Test dieser Spec, weil sein Ausfall echtes Geld kostet.
|
||||
5. **Die Kasse weist ab**, wenn dem aktiven Modus der Stripe-Schlüssel fehlt.
|
||||
6. **Migration:** ein `sk_test_` landet im Testplatz und setzt den Modus auf
|
||||
`test`; ein `sk_live_` landet im Live-Platz und lässt `live` stehen.
|
||||
7. **Webhook-Auswahl:** bei nicht erreichbaren Einstellungen wird der
|
||||
Live-Schlüssel benutzt, nicht der Test-Schlüssel.
|
||||
8. **Kein `wire:confirm`** im neuen Modal (R23 gilt schon repoweit).
|
||||
|
||||
---
|
||||
|
||||
## 8. Reihenfolge
|
||||
|
||||
1. `OperatingMode` + Migration + `SecretVault`-Auflösung (Kern, ohne Oberfläche)
|
||||
2. Stripe-Ausnahme + Kassensperre + Webhook-Auswahl
|
||||
3. `Readiness` mit den reinen Feldprüfungen
|
||||
4. Die drei Prüfungen mit Knopf
|
||||
5. Herzschläge
|
||||
6. Oberfläche: Umschalter, Bereitschaftsseite, Plakette, Übersichts-Hinweis
|
||||
|
||||
Schritt 1–2 ist der Teil, der falsch sein kann und Geld kostet. Schritt 3–6 ist
|
||||
Fleißarbeit.
|
||||
|
||||
---
|
||||
|
||||
## 9. Risiken
|
||||
|
||||
- **Der Rückfall ist bequem und gefährlich.** Für vier von fünf Einträgen
|
||||
richtig, für Stripe tödlich. Die `strict`-Ausnahme ist die einzige Zeile
|
||||
dieser Spec, deren Ausfall echtes Geld kostet — sie gehört in den Test, nicht
|
||||
in einen Kommentar.
|
||||
- **Der Modus in der Datenbank** ist eine neue Abhängigkeit für die
|
||||
Webhook-Prüfung, die bisher bewusst keine hatte. Entschärft durch den
|
||||
Vorgabewert `live` bei Nichterreichbarkeit (§4), aber es bleibt eine
|
||||
Abhängigkeit, die vorher nicht da war.
|
||||
- **Die Bereitschaftsseite kann Vollständigkeit vortäuschen.** Sie prüft, was
|
||||
jemand aufgeschrieben hat. Was niemand aufschreibt, bleibt unsichtbar — und
|
||||
genau das war der Befund vom 2026-07-30: eine grüne Testsuite bei einer
|
||||
Installation, die keinen Host durchinstallieren konnte. Deshalb Test 2, der
|
||||
wenigstens für Zugangsdaten erzwingt, dass nichts vergessen wird.
|
||||
|
||||
---
|
||||
|
||||
## 10. Folgepunkte
|
||||
|
||||
- **Testdaten-Trennung** (Teil C, beschlossen): Markierung an Auftrag, Vertrag,
|
||||
Beleg, Instanz und Kunde; eigene Rechnungsserie je Belegart für den
|
||||
Testbetrieb, damit die lückenlose `RE`-Serie sauber bleibt und Testbelege
|
||||
löschbar sind; Ausschluss aus Umsatz, Dashboard und Finanzansicht; ein
|
||||
Aufräumen, das die ganze Kette entfernt — **einschließlich der VMs auf dem
|
||||
Host und ihrer DNS-Einträge**. `EndInstanceService` nimmt Route und Eintrag
|
||||
weg, rührt die VM aber ausdrücklich nicht an; `ProxmoxClient::deleteVm()`
|
||||
existiert, der Aufrufer fehlt.
|
||||
- Die zwei Phantom-Hosts (`pve-fsn-1`, `pve-hel-1`) aus dem Pool nehmen. Die
|
||||
Bereitschaftsseite macht sie sichtbar, entfernt sie aber nicht.
|
||||
- Idempotenz-Schlüssel im Stripe-Abgleich deckt die Metadaten nicht ab —
|
||||
eigener Folgepunkt, am 2026-07-30 als Aufgabe abgelegt.
|
||||
Loading…
Reference in New Issue