5 Commits (e031d4c20af9f67f77a5d3c00a6e81975c6755f8)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
0475aad7b0 |
Fix-Runde 1: das Zeitfenster gilt nur, wo auch gedrosselt wird
Eine zeitliche Grenze hebelt --tries nicht bloss fuer Rueckstellungen aus, sondern ganz: Worker::markJobAsFailedIfWillExceedMaxAttempts prueft die Versuchszahl ausdruecklich nur if (! $job->retryUntil()). Damit galten die drei Versuche seit dem Takt NIRGENDS mehr — auch nicht auf der Direktspur und auch nicht bei ausgeschaltetem Notschalter. Eine Mail, die aus echtem Grund wirft (SMTP tot, PDF-Rendern kippt), wurde dadurch sechs Stunden lang wiederholt statt nach drei Versuchen abgelegt, und ohne Pause, weil der Arbeiter ohne --backoff laeuft. Ihr Fehler stand sechs Stunden lang nicht in failed_jobs, und auf der Direktspur haette eine einzige giftige Mail sechs Stunden lang die Kennwort-Zuruecksetzungen blockiert, sobald Aufgabe 4 den Arbeiter in Prioritaetsreihenfolge lesen laesst. retryUntil() haengt jetzt an derselben Bedingung wie die Drossel, beide lesen gedrosselteSpur(). Vorher wird die Elternklasse gefragt: SendQueuedMailable reicht an die Mailklasse weiter, und die Ueberschreibung nahm das still weg. Dazu zwei kleinere Punkte aus derselben Durchsicht: der Kopfkommentar behauptete weiter, eine middleware() auf der Mailklasse lese niemand — widerlegt am Vendor-Code, jetzt steht der echte Grund dort. Und (int) Settings::get() ergab auch ohne eingetipptes 0 eine Null (gespeicherte null, nichtnumerischer Wert); eine Untergrenze steht jetzt an der Lesestelle, an der der Verlust passiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
a713401562 |
Der Takt, und die Falle mit den Versuchen
SendQueuedMailable kennt keine middleware() — nachgesehen im Framework. Der Weg fuehrt ueber einen eigenen Auftrag, den Mailable::newQueuedJob() ueber den Container erzeugt; eine Bindung tauscht ihn fuer alle Mails aus, ohne dass eine Absendestelle sich aendert. mail-wichtig bekommt 30 je 5 Minuten, mail-ruhig 20 je 10, mail-direkt gar keine Drossel — dort wartet gerade ein Mensch. Der Arbeiter laeuft mit --tries=3, und eine gedrosselte Rueckstellung zaehlt als Versuch. Ohne retryUntil() waere jede Rechnung nach dem dritten Drosseln gescheitert statt verschickt. Der tragende Test faehrt den echten Arbeiter gegen ein zu kleines Kontingent und belegt beides: failed_jobs bleibt leer, und der hoechste Versuchszaehler ist vier — der Lauf hat die Linie wirklich ueberschritten. Die Bindung aendert den Klassennamen des eingereihten Auftrags, deshalb ziehen zwei bestehende Zusicherungen in MailLaneRoutingTest und SenderAddressTest nach: QueueFake legt Auftraege unter ihrem exakten Klassennamen ab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
200df30b9d |
Hetzner-Cloud-DNS, Bereitschaftsseite aus sich heraus behebbar
Die alte Hetzner-DNS-API ist abgeschaltet (301 auf die Weboberflaeche).
Jede Bereitstellung starb in ConfigureDnsAndTls, nachdem der Kunde bezahlt
hatte. HttpHetznerDnsClient und DnsTokenCheck sprechen jetzt die Cloud-API:
Bearer statt Auth-API-Token, RRSets ueber {name}/{typ} statt Record-IDs, der
Zonen-Lookup entfaellt. Gegen das Live-Konto gemessen, nicht geraten.
Drei Fallen, die am Konto gemessen wurden: TXT-Werte muessen in
Anfuehrungszeichen (sonst 422, was als read_only gemeldet worden waere), ein
Name mit Zonensuffix wird STILL angenommen (201), und der Fake gab andere IDs
aus als der echte Client -- zwoelf Tests waren gruen ueber einem Abbau, der im
Betrieb geworfen haette.
Bereitschaftspunkte, die nur eine Shell beheben konnte, sind jetzt bedienbar:
SSH-Schluesselpaar erzeugen (Ed25519 ueber phpseclib, privater Teil direkt in
den Tresor), Stripe-Katalog abgleichen (Warteschlange, Trockenlauf, Ausgabe
wortgleich), Mailzustellung als Schalter statt MAIL_MAILER, Neustart der
Arbeiterprozesse. Stripe hat jetzt alle drei Werte in der Konsole: Secret Key,
Signatur-Secret (Tresor, je Modus getrennt) und Publishable Key (Klartext).
Codex-Review (R15), zwei P1 behoben: der Signaturschluessel faellt nicht mehr
vom Live- in den Testplatz, und eine Record-ID aus der alten API macht einen
Host nicht mehr unloeschbar -- was adressierbar ist, wandelt eine Migration um,
der Rest wird beim Loeschen laut uebergangen statt geworfen.
2109 Tests gruen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
|
|
b3081a544b |
Let the export have as many destinations as somebody wants
tests / pest (push) Failing after 7m35s
Details
tests / assets (push) Successful in 21s
Details
tests / release (push) Has been skipped
Details
One path and one exported_at could not say "on the office NAS, not yet on the off-site box" — which is the only interesting question once there is more than one destination, and the reason for a second is precisely that any single one can fail. A row per destination now, and a row per (invoice, destination) pair. Two kinds, and the difference is where the protocol lives. 'local' is a directory this machine can already write to: a NAS mounted over NFS 4.1, a bind mount, a disk. The application knows nothing about the protocol, the target changes without code changing with it, and the whole thing stays testable against a temporary directory. 'sftp' is for something reachable with credentials — a Hetzner Storage Box being the obvious one — and the password goes into the secret vault with only its key on the row, because a credential in a settings table is a credential in every database dump. An empty password field on an existing target means unchanged, not cleared: the field cannot show what is stored, so blank-means-delete would wipe it on every unrelated edit. One job per pair rather than one per invoice. A job writing to both would retry the one that worked every time the other did not, and report one outcome for two different things. The ordering fix the tests found: Laravel's local adapter creates its root when it is CONSTRUCTED, so the "never create the root" guard was checking a directory the disk had just made for it. It runs before the disk is built now. That guard is the difference between an archive and a hole in the ground — mkdir on an absent mountpoint succeeds, writes onto the container's own disk and reports success, and an archive that silently is not the archive is worse than none. The single path from the previous commit is migrated into a destination called "Archiv" rather than dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
ab4b0311d4 |
Copy every invoice to the archive when it is issued, and notice when that fails
tests / pest (push) Failing after 11m24s
Details
tests / assets (push) Successful in 21s
Details
tests / release (push) Has been skipped
Details
The application knows nothing about NFS and should not. It writes to a directory; whether that is a local disk, a bind mount or a NAS over NFS 4.1 belongs to the mount. No protocol library, the target changes without code changing with it, and the whole thing is testable against a temp directory. The finding that shaped it: mkdir -p on a path whose mount is NOT there succeeds. It creates the empty directory beneath the mountpoint, writes the invoice onto the container's own disk and reports success — so an unmounted NAS would quietly collect invoices locally while every check said it worked. An archive that silently is not the archive is worse than none. The root is therefore never created, only used: a missing root means a missing mount, and that is a failure. Only the year subdirectory is created, inside a root already proven to exist. Written to a .part and renamed. A rename within one filesystem is atomic, so whatever watches that folder never sees a truncated PDF and takes it for a finished invoice. The size is read back afterwards rather than the write being trusted: over a network mount a short write can report success, and an archive of truncated files looks exactly like one that worked. Queued, not done during the request. A network mount can block for its whole timeout, and an invoice must not fail to be issued because a NAS is rebooting. Dispatched after the commit, or a worker could look for an invoice that has not landed yet. And copy-on-issue is not enough on its own: an invoice issued while the office connection was down never arrives, the queue eventually gives up, and nobody opens an archive to check whether last Tuesday is in it. An hourly sweep re-queues whatever is still missing, the failure is recorded on the invoice rather than only in a log, and the Finance page says how many are outstanding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |