Konzept: Geplante Aufgaben in calServer V2 (eigene Steuerung statt V1-Cronjobs)¶
Frage: Muss es das Cronjob-System in V2 so noch geben, oder gibt es eine modernere Anzeigeart dafür?
Kurzantwort: Nicht so, aber auch nicht nur als Anzeige. Der heutige
Cronjob-Editor unter /admin/operations bearbeitet eine V1-Tabelle, die in V2
niemand ausführt. V1-Werte anzuzeigen nützt der V2-Anwendung nichts. Sobald V1
abgeschaltet wird, braucht V2 eine eigene Steuerung für seine geplanten
Aufgaben, und die gibt es heute nicht: Der Zeitplan steht fest im Image, der
Scheduler ist im Auslieferungszustand aus, und beides ist in der Oberfläche
unsichtbar.
Der Entwurf, der das löst, ohne V1s Fehler zu wiederholen, steht in einem Satz: Der Katalog kommt aus dem Code, der Zeitplan aus der Datenbank.
1. Ist-Zustand¶
1.1 V1: Tabelle erzeugt Datei erzeugt crontab¶
In V1 ist die Kette vollständig und funktioniert:
| Schritt | Ort |
|---|---|
Datensatz in cron_job |
Adminpanel, Tab „Administration" |
afterSave()/afterDelete() schreiben die crontab-Datei |
AdminCronJob::updateCronJobFile() (httpdocs/protected/modules/adminpanel/models/AdminCronJob.php:208) |
/var/local/calServer-cron wird nach /etc/cron.d/calServer-cron kopiert |
ebd. Zeile 264 |
| Daemon lädt neu | AdminCronJob::restartCron() bzw. docker/app/restart_cron.sh |
| Ausführung | cron im V1-Container, Kommandos sind yiic <command> run |
Sechs Namen sind fest verdrahtet (DEFAULT_NAMES) und dürfen nicht umbenannt
werden: change_status, dms_inbox_check, mailer_action,
copy_file_to_inbox, backup, start_symmetricDS. Der Eintrag
mailer_action erzeugt beim Schreiben der Datei stillschweigend drei weitere
crontab-Zeilen (exportReport, sendEmailQueue, deleteReport), die in der
Oberfläche nirgends auftauchen. Das V1-Dockerfile trägt an der cron-Einrichtung
den Kommentar # FIXME: cron not working! (Dockerfile:110).
1.2 V2: der Zeitplan steht im Code, nicht in der Datenbank¶
V2 plant 13 verschiedene Kommandos in 14 Registrierungen, alle statisch:
laravel/routes/console.php: zwölf Einträge (counters:reset,mailer:process,notepad:send-reminders,notifications:prune,updates:check,license:check,status:apply-rules,status:rebuild-processing-times,metteam:pull,metteam:push,filter-statistics:save,demo:data autoreseed)laravel/bootstrap/app.php:dms:process-inbox(nur beidms.inbox.enabled) und ein zweiterdemo:data autoreseed
Ausgeführt werden sie von php artisan schedule:work als
Supervisor-Programm laravel-schedule, und das steht auf autostart=false
(docker/supervisor/laravel.conf:44). Bewusst so, begründet in
docs-v2/entwicklung/scheduler.md: der erste Lauf gegen gewachsene
Bestandsdaten ist der riskante Schritt.
1.3 Der Cronjob-Editor in V2 hängt an nichts¶
CronJobController bietet vollständiges CRUD auf cron_job plus
POST /admin/cron-jobs/restart. Was fehlt, ist der gesamte Rest der Kette:
| Voraussetzung | Zustand in V2 |
|---|---|
Jemand liest cron_job zur Ausführung |
Nein. CronJobController ist der einzige Verwender des Models im gesamten laravel/app/-Baum |
Jemand schreibt /var/local/calServer-cron |
Nein. Kein Gegenstück zu updateCronJobFile() |
cron im Container installiert |
Nein. Dockerfile.laravel installiert supervisor, kein cron |
/app/restart_cron.sh im Image |
Nein. Wird nicht hineinkopiert, der Restart-Endpunkt antwortet dort immer mit 422 |
| Vorbelegte Zeilen bei Neuinstallation | Nein. Weder db:install noch ein Seeder legt Cronjobs an; die Migration erzeugt nur die leere Tabelle |
Daraus drei Fehlbilder:
a) Leer auf frischer V2-Installation. „Keine Cron Jobs eingerichtet" ist die korrekte Aussage über eine Tabelle, die niemand füllt, und die falsche Aussage über die Anlage: 13 Aufgaben sind geplant.
b) Falsch auf einer aus V1 migrierten Installation. v1:sync kopiert
cron_job mit (SyncV1ToV2.php:317). Angezeigt werden dann yiic-Kommandos,
die im V2-Container nicht existieren, mit Zeitplänen, nach denen in V2 nichts
läuft.
c) Bearbeiten ist schlimmer als wirkungslos. Läuft V1 parallel, schreibt V2
die Tabellenzeile, ohne die crontab zu erneuern. Sobald in V1 das nächste Mal
irgendein Cronjob gespeichert wird, regeneriert updateCronJobFile() die Datei
aus der Tabelle und wendet die V2-Änderung nachträglich an. Sie wirkt also, nur
zu einem unvorhersehbaren Zeitpunkt und in der falschen Anwendung.
Dazu eine Falschaussage in der Doku: docs-v2/admin/betrieb.md Zeile 22 sagt
„Die mitgelieferten Jobs lassen sich nicht umbenennen — der Scheduler findet
sie über ihren Namen." Kein V2-Scheduler liest diese Tabelle. Die Namenssperre
stammt aus V1, wo sie die Log-Pfad-Zuordnung (DEFAULT_LOG_FILES)
zusammenhält.
2. Bewertung unter der Autarkie-Regel¶
Die Wurzelregel steht in der Root-CLAUDE.md: V2 löst V1 ab, statt es zu
begleiten. Damit ist die Frage nicht „Editor oder Anzeige", sondern: Was muss
V2 können, wenn V1 dunkel ist?
Die V1-Tabelle ist der falsche Gegenstand. Sie beschreibt yiic-Kommandos
in einem Container, den es nach dem Cutover nicht mehr gibt. Sie in V2
anzuzeigen ist bestenfalls Dekoration und schlimmstenfalls irreführend, sie in
V2 zu bearbeiten ist der Fehler aus 1.3c. Der Gegenstand gehört nicht
modernisiert, er gehört ersetzt.
Eine eigene Steuerung ist Pflicht, nicht Komfort. Ohne sie hat der Betreiber nach dem Cutover weniger Kontrolle als vorher:
| V1 heute | V2 heute | V2 nötig | |
|---|---|---|---|
| Sehen, was geplant ist | ja | nein | ja |
| Takt ändern | ja | nur per Redeploy | ja |
| Aufgabe abschalten | ja | nur per Redeploy | ja |
| Aufgabe jetzt ausführen | Testlauf | nein (nur Shell) | ja |
| Lief es, mit welchem Ergebnis | Log-Datei je Job | nein | ja |
| Läuft der Scheduler überhaupt | — | nein | ja |
Ein Kunde mit nächtlichem Wartungsfenster, der status:apply-rules nicht zur
vollen Stunde laufen lassen will, braucht heute ein eigenes Image. Das ist für
ein selbst gehostetes Produkt die falsche Antwort.
Der eine Teil von V1, der nicht mitkommt, ist das freie Kommandofeld. Ein
Formular, in das man ein beliebiges Kommando schreibt, das anschließend als
Systembenutzer ausgeführt wird, ist eine Rechteausweitung mit UI davor: Wer
administration_management hält, verschafft sich darüber alles, was der
Container kann. Das ist kein Grund gegen eine Steuerung, sondern die
Anforderung an ihren Zuschnitt.
3. Der Entwurf: Katalog aus dem Code, Zeitplan aus der Datenbank¶
Die Trennung, die alles Weitere trägt:
- Was laufen kann, liefert das Image. Jede planbare Aufgabe ist im Code registriert, mit fachlichem Namen, Beschreibung, Vorgabetakt und der Angabe, ob sie ohne Konfiguration wirkungslos ist. Kein Freitextfeld, aus dem ein Kommando wird.
- Ob und wann sie läuft, ist Mandantendaten. Die Datenbank hält Zustand und Abweichung vom Vorgabetakt, nicht den Katalog selbst.
Das löst die drei Probleme, an denen eine reine DB-Lösung sonst scheitert:
- Sicherheit. Planbar ist nur, was der Katalog kennt.
db:clearunddemo:data removestehen nicht darin,counters:resetschon. - Upgrades. Eine in Release N+1 hinzugekommene Aufgabe erscheint auf einer
bestehenden Installation automatisch, weil der Katalog aus dem Code kommt.
Läge er in der Datenbank, fehlte sie dort still — dieselbe Falle, die
laravel/CLAUDE.mdbeitranslations:resetbeschreibt: Migrationen laufen einmal, ein später ergänzter Schlüssel erreicht eine bereits migrierte Datenbank nie. - Nachvollziehbarkeit. Der Auslieferungszustand ist versioniert und testbar. Die Datenbank sagt nur, wo diese Installation davon abweicht, und „Auf Vorgabe zurücksetzen" ist eine Zeile löschen.
3.1 Datenmodell¶
Zwei neue V2-Tabellen, nach den Regeln aus laravel/CLAUDE.md (uuid-PK,
sprechende Spalten, DB-agnostische Typen, timestamps()):
scheduled_task — je Katalogeintrag höchstens eine Zeile, und nur, wenn
diese Installation abweicht:
| Spalte | Zweck |
|---|---|
id (uuid) |
PK |
task_key (string, unique) |
Schlüssel aus dem Katalog, z. B. mailer.process. Nicht das Artisan-Kommando, damit ein Umbenennen im Code die Daten nicht bricht |
enabled (bool, nullable) |
null = Vorgabe des Katalogs |
cron_expression (string, nullable) |
null = Vorgabetakt. Ein Feld, kein Fünferpack |
timezone (string, nullable) |
für Wartungsfenster; Vorgabe ist die Anwendungszeitzone |
updated_by (uuid, nullable) |
wer zuletzt gestellt hat |
timestamps |
scheduled_task_run — die Laufhistorie, die V1s Log-Dateien ersetzt:
| Spalte | Zweck |
|---|---|
id (uuid) |
PK |
task_key (string, index) |
|
started_at, finished_at |
|
duration_ms (unsigned int) |
|
status (string) |
success, failed, skipped |
exit_code (int, nullable) |
|
output (text, nullable) |
gekappt, nicht unbegrenzt |
triggered_by (string) |
schedule oder manual |
user_id (uuid, nullable) |
bei manuellem Start |
Aufbewahrung gehört von Anfang an dazu: Bei einem 15-Sekunden-Takt entstehen je
Aufgabe rund 5.700 Zeilen am Tag. Vorschlag: Erfolge kurz halten, Fehlläufe
lang, plus je Aufgabe immer den letzten Erfolg. Vorbild ist
notifications:prune.
3.2 Registrierung¶
Der Katalog ist eine Klasse (app/Support/ScheduledTaskCatalog.php, analog zum
bestehenden OperationCatalog) mit je Eintrag: task_key, Artisan-Kommando,
de/en-Label, Beschreibung, Vorgabe-Ausdruck, withoutOverlapping, und ein
Prüfer für „ohne Konfiguration wirkungslos" (MET/TEAM ohne Instanz, Demo-Reseed
ohne Demo-Modus).
withSchedule() in bootstrap/app.php iteriert den Katalog, holt die
Abweichungen in einer Abfrage und registriert daraus. routes/console.php
verliert seine feste Liste. Wichtig: Die Registrierung darf beim Bootstrap
keine fehlende Datenbank zum Fehler machen — vor der ersten Migration und in
CI-Läufen ohne DB muss sie stillschweigend auf die Vorgaben zurückfallen.
Ein Test, der Katalog und schedule:list gegeneinander hält, hält beides
zusammen (Vorbild: ReferenceLabelCoverageTest).
3.3 Steuerung über die API¶
Klein gehaltene Admin-Endpunkte, administration_management:
GET /admin/scheduled-tasks Katalog + Zustand + letzter/nächster Lauf
PUT /admin/scheduled-tasks/{key} enabled / cron_expression / timezone
DELETE /admin/scheduled-tasks/{key} auf Katalogvorgabe zurücksetzen
POST /admin/scheduled-tasks/{key}/run jetzt ausführen (Queue, nicht im Request)
GET /admin/scheduled-tasks/{key}/runs Laufhistorie
GET /admin/scheduler Gesamtzustand: Prozess, Pause, letzte Aktivität
POST /admin/scheduler/pause|resume schedule:pause / schedule:resume
cron_expression wird gegen einen Cron-Parser validiert und zusätzlich gegen
eine Mindestfrequenz, damit niemand versehentlich * * * * * auf einen
Vollbestandslauf setzt. „Jetzt ausführen" läuft über die Queue mit
TrackableJob, nicht synchron im Request — die vorhandene
long_running_tasks-Strecke ist genau dafür da.
Bewusst kein Endpunkt zum Anlegen freier Aufgaben. Wer eine neue geplante Aufgabe braucht, bekommt sie im nächsten Release als Katalogeintrag.
3.4 Oberfläche¶
Statt einer Grid-Tabelle mit fünf crontab-Spalten eine Liste, die sich wie ein Statusbericht liest und die Steuerung dort anbietet, wo der Zustand steht:
Geplante Aufgaben
┌──────────────────────────────────────────────────────────────────┐
│ Der Scheduler läuft nicht. [ Starten ] │
│ Keine der 13 geplanten Aufgaben wird ausgeführt. │
└──────────────────────────────────────────────────────────────────┘
E-Mail-Strecke [●] alle 5 Minuten ▾
● Bereit zuletzt vor 3 Min. (0,4 s) nächster 14:35 [ Jetzt ]
Zeitbasierte Statusregeln [●] stündlich ▾
● Fehler zuletzt vor 52 Min. (fehlgeschlagen) [ Details ]
MET/TEAM-Abgleich (eingehend) [○] alle 15 Sek. ▾
○ Inaktiv nicht konfiguriert
Bestandteile:
- Banner zum Gesamtzustand. Läuft
laravel-schedule? Ist perschedule:pauseangehalten? Das ist die wichtigste Einzelinformation der Seite und gehört nach oben, nicht in eine Spalte. - Ein Schalter je Aufgabe für an/aus. Kein Dialog dazwischen.
- Takt als Auswahl mit Klartext, nicht als fünf Textfelder: „alle 5 Minuten", „stündlich", „täglich um 03:20", „wöchentlich", plus „eigener Ausdruck" für den, der ihn braucht. Der rohe Ausdruck als Tooltip. Abweichungen von der Vorgabe sind als solche gekennzeichnet, mit „Zurücksetzen".
- Fachlicher Name statt Kommandoname. „E-Mail-Strecke", nicht
mailer:process. Das Kommando steht im Detail. - Zustand mit vier Werten: Bereit, Läuft gerade, Fehler, Inaktiv (Aufgabe registriert, aber ohne Konfiguration wirkungslos).
- Letzter Lauf relativ plus Dauer, nächster Lauf absolut.
- Detailbereich mit den letzten Läufen samt Ausgabe. Das ersetzt V1s Log-Dateien, ohne dass jemand Pfade konfiguriert.
- „Jetzt ausführen" je Aufgabe, mit Bestätigung bei bestandsweit wirkenden Aufgaben.
Das Muster ist im Haus etabliert und muss nicht erfunden werden:
long_running_tasks mit useLongRunningTask (Fortschritt und Zustandsautomat),
report_run_log und metteam_sync_log (Laufhistorie) sind dieselbe Form.
3.5 Technische Grundlage¶
Alles Bordmittel von Laravel 13, kein eigener Scheduler:
| Bedarf | Mittel |
|---|---|
| Was ist registriert, mit welchem Ausdruck, wann als Nächstes | app(Schedule::class)->events(), je Event nextRunDate() |
| Lief es, wie lange, mit welchem Ergebnis | Listener auf ScheduledTaskStarting / ScheduledTaskFinished / ScheduledTaskFailed / ScheduledTaskSkipped schreibt nach scheduled_task_run |
| Angehalten? | Cache-Schlüssel illuminate:schedule:paused (schedule:pause / schedule:resume) |
| Läuft der Prozess? | Heartbeat des Listeners, ergänzend supervisorctl status |
| Einzelne Aufgabe jetzt ausführen | Dispatch über die Queue als TrackableJob |
4. Autarkie-Lücken: was fehlt, bevor V1 dunkel werden kann¶
Die sechs V1-Standard-Cronjobs, jeder mit seinem V2-Stand:
| V1-Cronjob | V2-Äquivalent | Zustand |
|---|---|---|
change_status |
status:apply-rules |
vorhanden |
dms_inbox_check |
dms:process-inbox |
vorhanden |
mailer_action (+3 verdeckte Zeilen) |
mailer:process |
vorhanden |
copy_file_to_inbox |
— | entfällt in V2 (Entscheidung 2026-08-11). Kein Ersatz nötig |
backup |
— | muss gebaut werden, siehe 4.1 |
start_symmetricDS |
calserver-sync |
eigenständiger Client, bewusst außerhalb |
4.1 Sicherungen: konfigurierbar, aber ohne Ausführer¶
Sicherungen bleiben Bestandteil von V2 und müssen funktionieren. Heute tun sie das nicht. Was vorhanden ist und was fehlt:
| Teil | Zustand |
|---|---|
Tabellen backup_action, backup_borg |
vorhanden (aus V1 übernommen) |
CRUD-Endpunkte /admin/backup-actions |
vorhanden |
Tab „Sicherungen" in /admin/operations |
vorhanden |
| Klonen einer Konfiguration | vorhanden |
| Ausführer | fehlt. Nichts liest backup_action zur Ausführung |
| Testlauf | Attrappe. POST /admin/backup-actions/{id}/test-run lädt den Datensatz und antwortet „Backup test run initiated.", ohne etwas anzustoßen (AdminBackupActionController.php:97). Die Oberfläche meldet dem Benutzer Erfolg |
| Wiederherstellung | fehlt |
Aufbewahrung (max_backup) |
fehlt, wird gepflegt und nicht ausgewertet |
Die Attrappe gehört zuerst weg. Eine Erfolgsmeldung ohne Wirkung ist der schlechteste Zustand von allen: Der Betreiber glaubt, geprüft zu haben, dass seine Sicherung läuft.
Die Doku beschreibt einen Stand, den es nicht gibt.
docs-v2/admin/backup.md nennt vier Kommandos — backup:run, backup:test,
backup:restore, config:import — von denen keines existiert
(php artisan kennt sie nicht). Auch die dort beschriebenen Felder (Name, Typ,
Zeitplan, Speicherort, Aufbewahrung) stimmen nicht mit der tatsächlichen
Tabelle überein (description, start, end, dow, mo, dom,
max_backup, published). Das erklärt, warum der Stand „müsste doch laufen"
wirkt: Die Dokumentation sagt genau das.
Der Bauplan liegt im Repo. V1 hat die Umsetzung in BackupCommand
(liest backup_action, prüft Fenster und checkRunCron(), ruft
BackupDatabase + BackupFile), dazu CreateBackupCommand,
DownloadBackupCommand, RestoreDatabaseCommand und das jbackup-Modul mit
Borg. Und V2 hat für die Fälligkeitslogik bereits ein direktes Vorbild:
MailerActionRunner wertet mit dueActions() und matchesSchedule() exakt
dieselben Spalten aus (published, start, end, dow, mo, dom) und
trägt V1s checkRunCron-Semantik schon nach. Ein BackupActionRunner ist das
Schwestermodell dazu, kein neues Verfahren.
Damit fügt sich die Sicherung sauber in den Entwurf aus Abschnitt 3 ein: Der
Katalog bekommt eine Aufgabe backup.run mit einem Takt (Vorgabe: alle fünf
Minuten, wie V1), und die je Sicherung gepflegten Fenster und
Aufbewahrungsgrenzen bleiben da, wo sie hingehören — in backup_action, nicht
im Zeitplan der Aufgabe. Dasselbe Verhältnis wie zwischen mailer:process und
mailer_action.
Offen bleibt der Ablageort: V1 schreibt in ein per Property konfiguriertes Verzeichnis, V2 leitet seine Pfade aus der Container-Konfiguration ab. Für einen brauchbaren Betrieb gehört die Sicherung ohnehin aus dem Container heraus (Volume, S3, SFTP). Das ist zu entscheiden, bevor gebaut wird, und nicht Teil dieses Konzepts.
4.2 Der Scheduler ist im Auslieferungszustand aus¶
laravel-schedule steht auf autostart=false. Ein V2, das V1 ablöst, muss
den Scheduler an haben. Die Steuerung aus Abschnitt 3 macht den Trockenlauf aus
docs-v2/entwicklung/scheduler.md überprüfbar statt nur beschrieben und damit
autostart=true vertretbar: Der Betreiber sieht vor dem Einschalten, was
geplant ist, kann die riskanten Aufgaben einzeln aus lassen und sie nach dem
Trockenlauf gezielt dazuschalten. Heute ist es alles oder nichts.
5. Was mit dem Alten passiert¶
| Gegenstand | Vorschlag |
|---|---|
Tab „Cron Jobs" in /admin/operations |
ersetzen durch „Geplante Aufgaben" mit der Steuerung aus Abschnitt 3 |
POST /admin/cron-jobs/restart |
entfernen. Im V2-Image nicht ausführbar, im V1-Image die falsche Anwendung |
GET/POST/PUT/DELETE /admin/cron-jobs |
entfernen. Schreibzugriff zuerst, er ist der schädliche Teil (1.3c). V1-Werte anzuzeigen nützt der V2-Anwendung nichts, ein Read-only-Rest wäre also kein Kompromiss, sondern nur weniger Schaden |
Model App\Models\CronJob |
entfällt mit den Endpunkten |
Tabelle cron_job |
stehen lassen. Sie gehört V1, dort funktioniert sie, und die Autarkie-Regel verbietet Destruktives auf V1-Tabellen. Kein DROP, auch nach dem Cutover nicht — die V1→V2-Datenübernahme muss lauffähig bleiben |
cron_job in SyncV1ToV2 |
kann bleiben (kopiert V1-Daten für V1), ist nach dem Umbau aber wirkungslos für V2 |
| Cache-Bereich im selben Tab | bleibt, unabhängig und funktionsfähig |
docs-v2/admin/betrieb.md, Abschnitt „Cron Jobs" |
neu schreiben; die Aussage über den Scheduler ist falsch |
docs-v2/entwicklung/scheduler.md |
fortschreiben; nennt zwölf Aufgaben, listet elf, und license:check sowie filter-statistics:save fehlen ganz |
docs-v2/admin/backup.md (+ .en.md) |
neu schreiben. Beschreibt vier Kommandos, die es nicht gibt, und Felder, die die Tabelle nicht hat (4.1). Bis die Sicherung wirklich läuft, gehört dorthin der tatsächliche Stand, nicht der geplante |
Ein weiterer Nebenbefund, unabhängig von der Entscheidung:
demo:data autoreseed ist doppelt geplant, alle 15 Minuten in
routes/console.php:73 und stündlich in bootstrap/app.php:32. Beide tragen
withoutOverlapping(), die Verdopplung ist trotzdem unbeabsichtigt und
verschwindet mit dem Katalog von selbst.
6. Umsetzungsreihenfolge¶
Jeder Schritt ist für sich nutzbar, keiner setzt den nächsten voraus.
- Katalog + dynamische Registrierung.
routes/console.phpverliert die feste Liste, das Verhalten bleibt identisch. Ab hier existiert eine maschinenlesbare Liste der Aufgaben. Behebt nebenbei die Verdopplung. - Laufhistorie. Listener plus
scheduled_task_runplus Aufbewahrung. Ab hier ist beantwortbar, ob etwas lief. - Leseendpunkte + Seite. Katalog, Zustand, Historie, Gesamtzustand des Schedulers. Ab hier ist der Betreiber nicht mehr blind, und der alte Tab kann weg.
- Steuerung. An/aus, Takt, Zurücksetzen, Jetzt ausführen, Pause/Resume.
- Sicherungen bauen (4.1): Attrappe entfernen,
BackupActionRunnernach dem VorbildMailerActionRunner, Katalogeintragbackup.run, Aufbewahrung, Wiederherstellung. Läuft unabhängig von 1 bis 4 und kann parallel entstehen. autostart=truesetzen, wenn 1 bis 5 stehen.
Schritt 1 bis 3 sind die Voraussetzung dafür, Schritt 6 überhaupt
verantworten zu können. copy_file_to_inbox entfällt ersatzlos und taucht in
keinem Schritt auf.
7. Offene Punkte¶
- Wohin schreiben Sicherungen? V1 nimmt ein per Property konfiguriertes Verzeichnis, V2 leitet Pfade aus der Container-Konfiguration ab. Für brauchbaren Betrieb gehört die Sicherung aus dem Container heraus. Zu entscheiden, bevor 4.1 gebaut wird.
- Braucht die Taktänderung eine eigene Operation statt
administration_management? Wer Takte stellen darf, kann eine Aufgabe abschalten, die fachlich Pflicht ist (Mailversand, Statusregeln). Ein Audit-Eintrag je Änderung ist in jedem Fall angebracht. - Wie viel Laufhistorie, und wird sie mitgesichert oder ist sie wegwerfbar?
- Eigener Ausdruck oder nur Auswahl? Ein freier cron-Ausdruck ist mächtig und schwer zu validieren; eine reine Auswahl ist sicher, deckt aber Wartungsfenster wie „werktags 02:15" nicht ab. Vorschlag: Auswahl als Vorgabe, freier Ausdruck hinter „Erweitert" mit Mindestfrequenz-Prüfung.