Refuse workflows that move cards in a loop
Build package / xml-lint (push) Successful in 1m4s
Build package / php-lint (8.5) (push) Successful in 4m0s
Build package / unit-tests (push) Successful in 4m13s
Build package / php-lint (8.4) (push) Successful in 4m34s
Build package / package (push) Successful in 1m38s
Build package / xml-lint (push) Successful in 1m4s
Build package / php-lint (8.5) (push) Successful in 4m0s
Build package / unit-tests (push) Successful in 4m13s
Build package / php-lint (8.4) (push) Successful in 4m34s
Build package / package (push) Successful in 1m38s
Every enabled workflow is an edge "source stack -> target stack"; LoopDetector finds circles in that graph, a workflow whose source is its own target being the shortest. Without a check, two workflows A -> B and B -> A move the same cards back and forth on every run, even within a single run, with a notice for each move. The trigger date is ignored on purpose: by start date one way and by due date the other, a card with both dates passed still circles. Chains stay allowed. The controller rejects saving an enabled workflow that would close a loop and names the workflows involved. Workflows stored before this check are skipped by the runner (logged, not disabled) and flagged in the settings list via a new `loop` field from index().
This commit is contained in:
1 parent
8268f1068d
commit
9e1a15bab0
8 files changed
+251
-5
No files matched your search
@@ -25,7 +25,7 @@ Alle Lesezugriffe (Boards, Stacks, Karten) und das Verschieben laufen über Deck
|
||||
|
||||
**Regel:** Bevor ein weiterer `OCA\Deck\*`-Dienst im Runner genutzt wird, prüfen, ob er eine eingefrorene `$userId` trägt – der Fehler ist still und nutzerübergreifend. Bevorzugt Mapper (QBMapper ohne Nutzerzustand) oder Methoden mit explizitem uid-Parameter verwenden. Deshalb wird `BoardService::getUserBoards()` nicht verwendet.
|
||||
|
||||
Der Datumsvergleich `WorkflowRunner::hasPassed()`, `TriggerDate` und die Empfängerregel `NotificationPolicy::shouldNotify()` sind bewusst `static` und seiteneffektfrei, also ohne Deck unit-testbar (`tests/Unit/Service/`). Das sind die einzigen getesteten Teile der App. Neue reine Logik in dieser Form halten.
|
||||
Der Datumsvergleich `WorkflowRunner::hasPassed()`, `TriggerDate`, `LoopDetector` und die Empfängerregel `NotificationPolicy::shouldNotify()` sind bewusst `static` und seiteneffektfrei, also ohne Deck unit-testbar (`tests/Unit/Service/`). Das sind die einzigen getesteten Teile der App. Neue reine Logik in dieser Form halten.
|
||||
|
||||
## Workflows werden deaktiviert, wenn Board/Stack verschwinden
|
||||
|
||||
@@ -79,6 +79,14 @@ Die App wird nur intern genutzt und spricht ausschließlich Deutsch. Oberfläche
|
||||
- **Texte:** `TriggerDate::describeReason()` liefert den Satzteil („ist überfällig“ / „hat ihr Startdatum erreicht“) für Mail und Notifier. `NotificationPusher` speichert `triggerDate` in den Subject-Parametern. Ältere Benachrichtigungen ohne den Schlüssel gelten als `due`.
|
||||
- **Empfänger:** Auch Startdatums-Workflows nutzen bei `notify_mode = board` Decks `notify-due`-Einstellung. Deck hat keine eigene für das Startdatum.
|
||||
|
||||
## Keine Schleifen, kein Quelle = Ziel
|
||||
|
||||
Jeder aktive Workflow ist eine Kante „Quell-Stack → Ziel-Stack“. `LoopDetector` (rein, getestet) findet Kreise. „Quelle = Ziel“ ist ein Kreis der Länge 1. Das Auslöse-Datum wird bewusst ignoriert: Bei S1→S2 per Start und S2→S1 per Fälligkeit pendelt jede Karte, bei der beide Daten erreicht sind. Ohne Prüfung würde der Runner sie sogar im selben Lauf hin- und herschieben (jeder Workflow liest seinen Quell-Stack neu), mit einer Benachrichtigung pro Bewegung. Ketten sind erlaubt.
|
||||
|
||||
- **Controller:** `validate()` → `assertNoLoop()` lehnt einen *aktiven* Workflow ab, dessen Ziel über die anderen aktiven Workflows des Boards zu seiner Quelle zurückführt, und nennt sie. Deaktivierte Workflows zählen nicht. Beim Aktivieren läuft dieselbe Prüfung, weil auch das über `update()` geht.
|
||||
- **Runner und Liste:** Altbestand kann trotzdem Kreise bilden. `run()` überspringt alle Workflows auf einem Kreis (nur Log, keine Deaktivierung, keine Mail), und `index()` liefert `loop: true`, das die Oberfläche rot markiert.
|
||||
- Stack-IDs sind in Deck boardübergreifend eindeutig, deshalb darf der Runner alle Workflows der Instanz in einen Graphen werfen.
|
||||
|
||||
## Fälligkeit wird als UTC-Zeitpunkt verglichen
|
||||
|
||||
Decks `duedate` und `startdate` sind `DATETIME`-Spalten ohne Zeitzone mit UTC-Uhrzeit (Frontend sendet `toISOString()`). `Entity` baut daraus `new \DateTime($string)` in PHPs Standardzone, die Nextcloud auf UTC setzt. `WorkflowRunner::asStoredUtc()` legt die Uhrzeit trotzdem ausdrücklich auf UTC fest, damit das Ergebnis nicht von dieser Standardzone abhängt. Decks eigene Pfade `Card::getDaysUntilDue()` (tagesgenau) und `CardMapper::findOverdue()` (vergleicht mit dem `NOW()` der Datenbank, also Ortszeit) sind dafür ungeeignet und werden nicht verwendet.
|
||||
|
||||
Reference in new issue
Block a user