archive_volid: speichert den Ort des Archivs, ohne den es nach 12 Monaten
nicht wiederzufinden ist.
archived_at: speichert den Zeitpunkt der Archivierung, daraus berechnet sich
die 12-Monats-Frist.
torn_down_at: speichert den Zeitpunkt der Maschinenlöschung, getrennt von
archived_at, damit der Zwischenstand sichtbar bleibt, wenn Archivierung
erfolgreich war aber Löschung noch nicht versucht oder gescheitert ist.
teardown_error: speichert die Fehlermeldung beim Löschen in Klartext, damit
ein Betreiber ohne Log-Suche reagieren kann.
Vier Tests zeigen, dass die Felder auf null stehen, in Carbon casten und
dass der Zwischenstand (archiviert, nicht abgebaut) ein gültiger Zustand ist.
Eine Mutation-Prüfung bestätigt, dass die Tests greifen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests / release (push) Blocked by required conditionsDetails
Zwei Nachwirkungen der ersten Fix-Welle (c9fef59).
1 — Die Regression, die sie selbst gesetzt hat. `endedAt()` fragte nur
„laeuft gerade nichts?" und nahm dann die juengste beendete Instanz. „Nichts
in Betrieb" ist aber nicht dasselbe wie „mit uns fertig": eine frisch
bestellte Instanz entsteht als `reserving` (ReserveResources) und bleibt das
den ganzen Bereitstellungslauf lang, `failed` (Order::markFailed) steht, bis
ein Betreiber wiederholt — beide fehlen in der Betriebs-Abfrage. Ein
wiederkehrender Kunde, der eben bezahlt hatte, las deshalb „Ihre Cloud ist
beendet." samt „Neues Paket buchen", direkt ueber dem Streifen, der seinen
laufenden Aufbau zeigte.
Jetzt muss die JUENGSTE Instanz des Kunden die beendete sein. Alle sechs
Instanzzustaende nachgeprueft (reserving, provisioning, active, failed,
cancellation_scheduled, ended; `suspended` ist seit 2026_07_31_160000 keiner
mehr, sondern eine eigene Marke) — jeder ausser `ended` ganz oben heisst: es
geschieht etwas Neueres.
Dazu eine zweite Bedingung, die die erste allein nicht traegt:
ReserveResources parkt eine bezahlte Bestellung ohne Platz und legt dabei GAR
KEINE Instanz an, bis zu vierzehn Tage. Die juengste Instanz ist in diesem
Fenster weiterhin die alte, beendete. Der Kasten bleibt deshalb weg, solange
fuer den Kunden ein Aufbau laeuft — dieselbe Bedingung, unter der
CustomerProvisioning den Fortschrittsstreifen zeigt. Zwei Bauteile auf einer
Seite duerfen einander nicht widersprechen.
2 — Das Versprechen stand noch dort, wo entschieden wird. Die erste Welle hat
drei von vier Stellen zurueckgenommen; die vierte war der Kuendigungsdialog
selbst. Der Kunde las dort einen Liefergegenstand („einen Link zum
Herunterladen", „erhalten Sie ... einen vollstaendigen Datenexport"), klickte
einmal auf Ja — und die Vertragsseite sagte danach nur noch „Ihr Wunsch ist
vermerkt, wir melden uns". Beide Saetze sind jetzt auf dasselbe Mass gebracht:
kein Liefergegenstand, keine Frist, kein Link, solange der Export nicht gebaut
ist. Der wahre Teil bleibt wortgleich stehen — ab dem Laufzeitende ist die
Cloud nicht mehr erreichbar, bis dahin selbst sichern.
lang/{de,en}/order.php bleibt absichtlich unveraendert: dieser Satz steht im
Verkauf und beschreibt, was das Produkt zusagt. Ihn zu streichen aendert das
Angebot und ist eine Entscheidung des Eigentuemers. Begruendung im Bericht.
Zu beiden Befunden wurde der Fix zurueckgedreht und die neue Zusicherung rot
gesehen (3 rot beim Waechter, 2 rot bei den Sprachdateien), dazu die
Gegenprobe mit einem zu breiten Waechter (1 rot). Ganze Suite: 2842 gruen,
0 rot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vier Befunde aus dem Gesamt-Review, und alle vier hatten dieselbe Wurzel:
`export_wish` fuehrt drei Zustaende, und jede Stelle, die den Kunden
ansprach, kannte nur zwei.
K1 — Der Streifen im Dashboard trug einen Schalter, und `(bool) null` ist
`false`. Wer vor dieser Ausrollung gekuendigt hat, las unter dem Streifen
„Kein Export gewuenscht" — als waere das seine eigene Antwort. Jetzt stehen
dort dieselben zwei Auswahlfelder wie im Kuendigungsdialog: gleiche Frage,
gleiche Form, und ein unbeantworteter Zustand markiert schlicht keines von
beiden. Der Satz daneben fragt dann, statt zu behaupten, und sagt, was
passiert, wenn die Frage offen bleibt.
K2 — Die Vertragsseite versprach jedem den Export, auch dem, den der Dialog
eine Sekunde vorher mit einem bewussten „Nein" genau dorthin umgeleitet
hatte. Drei Fassungen statt einer, an `export_wish` gebunden. Die Ja-Fassung
verspricht dabei nicht mehr den Export selbst, sondern dass der Wunsch
vermerkt ist und sich jemand meldet — den Export gibt es nicht, und ein
gebundenes, aber weiterhin unhaltbares Versprechen haette den Fehler nur
verschoben.
Dazu der Zustand danach: eine `ended`-Instanz holt Dashboard::render() nicht
mehr, und der Kunde fiel in denselben Zweig wie jemand, der noch nie etwas
bestellt hat — „Ihre Cloud wird eingerichtet." samt „Paket buchen", am Tag,
an dem ihm die Adresse eingezogen wurde. Der Fall hat jetzt seinen eigenen
Kasten, mit dem Datum, an dem das Paket endete.
W1 — Die Erinnerungsmail behauptete im Praesens, wir bereiteten bereits einen
Export vor. Der Satz sagt jetzt, was stimmt. Und die Antwort hatte in der
ganzen Konsole keinen einzigen Leser: ein „Ja" landete in einer Spalte, die
niemand je zu Gesicht bekam. Ueber der Instanzliste steht deshalb ein
Abschnitt „Datenexport bestellt" — wer, und bis wann. Nicht als Plakette in
der Zeile, weil die Liste geblaettert ist und ein alter Eintrag auf Seite acht
saesse; nicht auf der Uebersicht, weil ein Hinweis, den nichts je wieder
abraeumen kann, Moebel waere.
W3 — Die einzige Pruefung zur Anzeige der Antwort konnte nicht fehlschlagen:
`x-ui.switch` rendert beide Woerter und ueberlaesst dem CSS die Auswahl, also
war `assertSee('Kein Export gewuenscht')` bei true, bei false UND bei null
gruen. Nachgewiesen mit einer Wegwerf-Pruefung gegen den alten Streifen:
dreimal derselbe Satz, dreimal gruen. Jetzt drei Pruefungen, je eine pro
Zustand, am `checked`-Attribut der Auswahlfelder.
Zu jedem der vier Punkte wurde der Fix kurz zurueckgedreht und die neue
Zusicherung rot gesehen; die Ergebnisse stehen im Bericht.
Ganze Suite: 2819 gruen, 2 rot — beide fremd. ReadinessPageTest scheitert an
`server.private_key` aus der parallel laufenden Terminal-Arbeit im selben
Baum; HostStepTest ist auf main vorbestehend rot (install-agent.sh traegt
CONTRACT=3, update.sh HOST_STEP_NEEDS=2, beide unveraendert).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eine Mail sieben Tage vorher kann im Postfach untergehen; die eigene Uebersicht
oeffnet der Kunde ohnehin. Der Streifen nennt das Datum, die Restzeit und was
danach geschieht — dass der Zugang zur Cloud endet, nicht dass irgendetwas
geloescht wird. Das ist die Frist, die ihn betrifft.
Und er traegt die Antwort zum Export: wer liest, dass die Zeit laeuft, will es
sich im selben Atemzug anders ueberlegen koennen (Aufgabe 4) — bis zum
Laufzeitende, danach nicht mehr, geprueft direkt an der Methode und nicht nur
am Schalter.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests / release (push) Blocked by required conditionsDetails
$instance->update(['export_reminded_at' => now()]) lag ausserhalb des
try/catch, das nur den Mailversand absicherte. Ein Fehlschlag dieses einen
Schreibzugriffs haette die Marke leer gelassen (morgen eine zweite Mail fuer
dieselbe Instanz) UND den ganzen Lauf abgebrochen, statt nur diese eine
Instanz zu ueberspringen. Jetzt beides in derselben Absicherung.
Dazu zwei Pruefungen, die der Rand "Ende schon vorbei" bislang nicht hatte:
keine Erinnerung mehr, wenn die Frist schon abgelaufen ist (kein akademischer
Fall - EndInstanceService laesst einen DNS-Fehler bewusst durch, eine
Instanz kann also real tagelang mit abgelaufener Frist stehenbleiben), und
die obere Einschlussgrenze bei genau sieben Tagen, damit beide Raender der
"hoechstens sieben Tage"-Regel durch je eine eigene Pruefung belegt sind.
Ausserdem: die Selbst-Herunterladen-Adresse selbst wird jetzt im Rendertest
geprueft, nicht nur Datum und Sprachschluessel.
Ab dem Laufzeitende zieht EndInstanceService die Adresse ein — der Kunde kommt
ab dem Moment nicht mehr an seine Nextcloud. Wer selbst etwas herunterladen
will, muss es VORHER tun, und das wusste bisher niemand.
Die Auswahl ist bewusst "hoechstens sieben Tage" und nicht "genau sieben Tage":
ein Gleichheitsvergleich verfehlt jede Instanz, die zwischen zwei Laeufen
durchrutscht, und der Preis dafuer waere, dass jemand ausgesperrt wird, ohne es
gewusst zu haben.
Zusaetzlich zum Zettel: lang/{de,en}/mail_pace.php bekommen einen Eintrag fuer
ServiceEndingSoonMail, weil MailPacePageTest fuer jede Klasse in MailLane::all()
einen Anzeigenamen verlangt — ohne ihn waere die Suite rot.
tests / release (push) Blocked by required conditionsDetails
Befund 1: neuer deutscher Text (Kommentare, ein Testname) benutzte ae/oe/ue/ss
statt ä/ö/ü/ß — der Bestand schreibt mit echten Umlauten, das Original wird
hier nachgezogen. Betroffen: die drei Kommentare in ConfirmCancelPackage.php,
der Blade-Kommentar, sowie Kopfkommentare und ein Testname in
CancelAsksAboutExportTest.php. Der Funktionsname kuendbareInstanz() und die
Test-Fixtures (E-Mail/Subdomain "kuendigt") bleiben ASCII — ersterer ist
wörtlich aus dem Zettel übernommen, letztere sind technische Werte wie jede
andere Test-Subdomain im Bestand (acme, berger).
Befund 2 (Entscheidung des Betreibers): cancel_point_export versprach den
Datenexport unbedingt, direkt über einer Frage, die ihn an ein Ja knüpft. "auf
Wunsch" eingefügt, in beiden Sprachdateien — der Satz sagt jetzt, was der
Dialog tatsächlich tut, ohne sonst etwas am Text zu ändern.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Angelpunkt des ganzen Vorhabens: wer keinen Export braucht, loest keine
Arbeit aus und wartet auf nichts. Zwei Auswahlfelder und keine Checkbox — eine
Checkbox kennt keinen dritten Zustand, und "nicht angekreuzt" waere von "nein"
nicht zu unterscheiden.
Die Frage ist nicht ueberspringbar (#[Validate('required|boolean')], vor Stripe
geprueft wie jede andere Vorbedingung hier). Das macht drei bestehende
Erfolgspfad-Pruefungen zu ConfirmCancelPackage neu pflichtig in einem Feld, das
sie vorher nicht kannten — PackageCancellationTest, SettingsTest und
EndInstanceServiceTest setzen deshalb jetzt zusaetzlich exportWish, ohne dass
sich an ihren eigentlichen Zusicherungen etwas aendert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests / release (push) Blocked by required conditionsDetails
Drei Zustaende, nicht zwei: `null` heisst "noch nicht gefragt". Jede Instanz,
die vor diesem Bau gekuendigt wurde, hat die Frage nie gesehen — ihr ein "nein"
zu unterstellen hiesse, still fuer Menschen zu antworten, die niemand gefragt
hat.