Specyfikacja Time Guards: Hibernacja, Skoki Zegara i Przejście przez Północ

Kompletna specyfikacja techniczno-biznesowa. Dokument opisuje wszystkie przypadki, w których system zatrzymuje, dzieli lub koryguje naliczanie czasu pracy, wraz z przykładami end-to-end.

Spis treści:

1. Zakres i Słownik

Dokument opisuje zachowanie mechanizmów ochronnych (time guards), które automatycznie kończą lub dzielą wpis czasu pracy w sytuacjach granicznych: uśpienie/hibernacja komputera, skoki zegara systemowego oraz przekroczenie granicy doby.

Słownik pojęć

Pojęcie Znaczenie operacyjne
LastKnownGoodUtc Ostatni bezpieczny punkt czasu (heartbeat), na którym można wiarygodnie zamknąć wpis.
Resume Moment wybudzenia systemu po śnie/hibernacji.
Dual-entry Strategia zapisu dwóch wpisów: (1) czas pracy do snu, (2) czas snu jako nieaktywność.
Midnight Guard Mechanizm zamykania/rozgałęziania wpisów na granicy doby (23:59:59 / 00:00:00).

Statusy FinishStatus używane w tej specyfikacji

ID Nazwa enum Znaczenie biznesowe
2 AutoFinishAfterAppRestart Automatyczne domknięcie lokalnego aktywnego wpisu po restarcie aplikacji (gdy wpis nie był powiązany z aktywną sesją serwera).
4 AutoFinishDueToAppUpdate Techniczne domknięcie wpisu przy aktualizacji aplikacji lub przy porządkowaniu duplikatów aktywnych wpisów.
3 AutoFinishAfterSystemRestart Fallback/watchdog domykający wpis po granicy doby, gdy standardowe mechanizmy midnight/hibernacji nie zadziałały.
1 FinishByUser Ręczne zakończenie przez użytkownika (stosowane w docelowym wariancie „NIE” w midnight guard).
5 AutoFinishDueToUserInactivity Automatyczne zakończenie jako nieaktywność (m.in. drugi wpis dual-entry).
10 AutoFinishDueToClocksDifferences Auto-zatrzymanie po wykryciu anomalii zegara systemowego.
11 AutoFinishDueToHibernation Auto-zatrzymanie po wykryciu uśpienia/hibernacji.
12 AutoFinishDueToMidnightCrossing Zamknięcie wpisu o 23:59:59 na granicy doby.
69 FinishedOrBlockedOnServerBackend Status techniczny związany z synchronizacją backend/local; wykorzystywany podczas rozstrzygania konfliktów stanu.

2. Parametry i Flagi Konfiguracyjne

Parametry w appsettings.json:

Klucz konfiguracji Wartość domyślna Rola biznesowa
HibernationHeuristicThresholdSeconds 120 (sekund, 2 minuty) Minimalny czas odjazdu zegara, poniżej którego zdarzenie jest traktowane jako szum.
ClockDiscrepancyThresholdMinutes 5 (minut) Maksymalna dopuszczalna rozbieżność zegarów przed uznaniem anomalii.
HibernationGuardSkipDelegation true Czy zadania oznaczone jako delegacja mają być pomijane przez guard hibernacji.
MidnightGuardPromptMinutes 30 (minut) Aktywny offset pokazywania pytania o kontynuację przed północą.
MidnightGuardAutoContinueIfStartedWithinMinutes 15 (minut) Jeśli zadanie wystartowało w tym progu przed 00:00, dialog jest pomijany i następuje auto-kontynuacja.
LongWorkingSessionLimitHours 4 (godziny) Limit ciągłej sesji dla LongWorkingSessionGuard, liczony również przez północ po potwierdzeniu kontynuacji.
HibernationDualEntryEnabled (feature flag) false (lub true) Przełącznik aktywujący drugi wpis nieaktywności po krótkiej hibernacji.
HibernationDualEntryMaxDurationMinutes (feature flag) 60 (minut) Maksymalna długość hibernacji kwalifikująca się do zapisu wpisu nr 2.

Diagnostyka: dla ścieżki dual-entry używany jest znacznik logów HIBERNATION_DUAL_ENTRY.

3. Hibernacja i Skoki Zegara — Tryb bazowy (dual-entry OFF)

W trybie bazowym system zawsze zapisuje pojedynczy wpis roboczy zakończony na ostatnim bezpiecznym momencie sprzed anomalii (heartbeat / LastKnownGoodUtc).

Schemat obecnego podziału czasu (zawsze 1 wpis)
Wpis 1: Czas pracy faktyczny
Okres hibernacji (niezapisany / odrzucony)
Start zadania (np. 10:00) Ostatni heartbeat sprzed snu (np. 12:00) Wybudzenie PC (np. 12:45)
Krok Przedział czasu Status zapisu (FinishStatus) Zachowanie systemu
Wpis 1 Start zadania → Ostatni znany czas (LastKnownGoodUtc) 11: AutoFinishDueToHibernation
(lub 10: AutoFinishDueToClocksDifferences)
Zapis do SQLite i synchronizacja z API. Tylko czas realnie przepracowany.
Czas uśpienia Ostatni znany czas → Czas wybudzenia Brak wpisu Czas ten przepada. Zadanie nie wznawia się automatycznie po wybudzeniu.

4. Hibernacja — Tryb dual-entry ON (stan wdrożony)

Dla krótkich hibernacji (od progu reakcji HibernationHeuristicThresholdSeconds, domyślnie 120s, do progu maksymalnego HibernationDualEntryMaxDurationMinutes, domyślnie 60 min) system rejestruje dwa rozłączne wpisy, aby czas uśpienia pracownika był odnotowany w systemie jako nieaktywność. Ścieżka jest sterowana przełącznikiem HibernationDualEntryEnabled i może być natychmiast wyłączona.

Schemat nowego podziału czasu (Dual-Entry: 2 wpisy)
Wpis 1: Praca właściwa
Wpis 2: Czas hibernacji (Nieaktywność)
Start zadania (np. 10:00) Zakończenie przed uśpieniem (12:00) Wybudzenie PC (12:45)

Warianty zależnie od długości hibernacji:

< próg heurystyczny

Poniżej HibernationHeuristicThresholdSeconds. Zdarzenie traktowane jako szum/czas zbyt krótki. Brak dual-entry.

Próg heurystyczny ≤ Czas ≤ próg maksymalny (Dual-Entry)

Wpis 1: Start → sen (Status 11)
Wpis 2: sen → wybudzenie (Status 5: Inactivity)

> próg maksymalny (Długa hibernacja)

Tylko Wpis 1 (Status 11). Okres snu powyżej HibernationDualEntryMaxDurationMinutes nie jest zapisywany jako osobny wpis nieaktywności.

Uwaga implementacyjna: dual-entry dotyczy wyłącznie ścieżki AutoFinishDueToHibernation. Dla anomalii zegara (AutoFinishDueToClocksDifferences) system nie tworzy drugiego wpisu. W logach używany jest znacznik: HIBERNATION_DUAL_ENTRY.

Wpis Początek Koniec Status (FinishStatus) Opis biznesowy
Wpis 1 Start zadania Ostatni czas aktywności (przed uśpieniem) 11: AutoFinishDueToHibernation Rzeczywisty czas przepracowany przez pracownika przed odejściem od komputera.
Wpis 2 Koniec Wpisu 1 (moment uśpienia) Czas wybudzenia (Resume) 5: AutoFinishDueToUserInactivity Zarejestrowany czas nieobecności pracownika z automatycznym statusem nieaktywności.

Reguła przekroczenia północy dla wpisu nr 2 (dual-entry)

Jeśli drugi wpis nieaktywności (status 5) obejmuje granicę doby, system dzieli go na dwa rekordy dzienne: ... → 23:59:59 oraz 00:00:00 → .... Dzięki temu pojedynczy rekord nie przechodzi przez północ.

5. Midnight Guard — Stan aktualny (kod produkcyjny)

Poniżej opis zachowania, które obecnie wynika bezpośrednio z implementacji i powinno być traktowane jako „as-is”.

Scenariusz A (aktualnie): Start w oknie (po 23:45)

Użytkownik uruchomił zadanie np. o 23:50.
• Brak okna dialogowego (nie przeszkadzamy).
• O 23:59:59 wpis jest zamykany (Status 12).
• O 00:00:00 automatycznie startuje kontynuacja.

Scenariusz B (aktualnie): Start wcześniej (np. 20:00)

O 23:45 pojawia się okno z pytaniem i odliczaniem do północy.
• Odpowiedź „TAK”: o 00:00:00 zadanie biegnie dalej w nowym wpisie.
• Odpowiedź „NIE”: natychmiastowe zakończenie wpisu w chwili kliknięcia.
• Status końca: 1: FinishByUser.

Scenariusz C (aktualnie): Brak reakcji / Hibernacja

Jeśli użytkownik odszedł od PC i minęła północ (lub komputer zasnął w trakcie okna):
• Zadanie zostaje definitywnie zatrzymane o 23:59:59.
• Nowy wpis po północy NIE powstaje.
• Flaga zakończenia: 5: AutoFinishDueToUserInactivity.

Oś czasu przejścia przez północ z potwierdzeniem (Scenariusz B - TAK)
Wpis Dnia Poprzedniego (Start → 23:59:59) [Status 12]
Wpis Nowego Dnia (00:00:00 → ...) [Nowe zadanie]
Start zadania 23:45: Okno dialogowe 00:00: Granica doby

6. Midnight Guard — Zachowanie docelowe (wdrożone)

Poniższe reguły opisują stan wdrożony, który jednoznacznie oddziela decyzję użytkownika od automatycznych zakończeń.

Scenariusz A (docelowo): „TAK” na kontynuację

• O 23:59:59 zamykany jest wpis dnia poprzedniego (Status 12).
• O 00:00:00 tworzy się nowy wpis kontynuacji.

Scenariusz B (docelowo): „Zakończ teraz”

• Zakończenie następuje natychmiast po kliknięciu.
• Status końca: 1: FinishByUser.

Scenariusz C (docelowo): Brak reakcji użytkownika

• O 23:59:59 zadanie zamykane automatycznie.
• Status końca: 5: AutoFinishDueToUserInactivity.
• Brak wpisu kontynuacji po północy.

Okno pre-midnight jest sterowane wyłącznie przez MidnightGuardPromptMinutes (aktualnie 30 minut).

7. Przykłady end-to-end

Przykład A: Hibernacja 12:00–12:20, dual-entry ON

Wpis Przedział Status
Wpis 1 10:00 → 12:00 11
Wpis 2 12:00 → 12:20 5

Przykład B: Hibernacja 23:50–00:10, dual-entry ON

Wpis Przedział Status
Wpis 1 Start → 23:50 11
Wpis 2a 23:50 → 23:59:59 5
Wpis 2b 00:00:00 → 00:10 5

Przykład C: Midnight Guard — wynik dla kliknięcia „Zakończ teraz” i braku reakcji

Sytuacja Moment zamknięcia Status
Kliknięcie „Zakończ teraz” Natychmiast po kliknięciu 1
Brak reakcji do północy 23:59:59 5

8. Komunikaty Wyświetlane Użytkownikowi (Okna Dialogowe)

Zasada komunikacji: przed północą komunikat ma mówić o zbliżającej się północy i odliczać czas do 23:59:59. Dopiero po faktycznym minięciu północy pokazujemy informację, że północ minęła, wraz z danymi zamkniętego wpisu.

Stan wdrożony: kliknięcie „Zakończ teraz” zamyka wpis natychmiast ze statusem ręcznym, a brak reakcji kończy wpis o 23:59:59 jako nieaktywność.

9. Koordynacja guardów (LongWorkingSession vs Midnight)

Reguła Stan Efekt
Midnight guard aktywny/pending blokuje LongWorkingSessionGuard Wdrożone Brak nakładania okien ostrzegawczych i spójny przebieg decyzji użytkownika.
Ciągłość sesji przez północ po potwierdzeniu „TAK” Wdrożone Long guard liczy limit ciągłej pracy (domyślnie 4h) przez granicę doby.
Start zadania w progu MidnightGuardAutoContinueIfStartedWithinMinutes Wdrożone Brak dialogu pre-midnight; automatyczny split o 23:59:59/00:00:00 i kontynuacja.

10. LongWorkingSessionGuard — długa ciągła praca

Guard długiej sesji ostrzega po przekroczeniu limitu LongWorkingSessionLimitHours (domyślnie 4h). Liczenie jest prowadzone na podstawie ciągłego początku sesji i nie resetuje się na północy, jeśli użytkownik potwierdził kontynuację w Midnight Guard.

Scenariusz Co robi system Wpływ na WorkTime
Sesja ciągła bez midnight guard Po upływie limitu pojawia się okno LongWorkingSessionGuard z akcjami: TAK, Zakończ teraz, Zakończ o HH:mm:ss. Decyzja użytkownika steruje dalszym naliczaniem lub zatrzymaniem.
Midnight guard aktywny/pending Long guard jest tymczasowo tłumiony, aby nie nakładać okien. Brak konfliktu zapisów i jeden spójny przebieg decyzji.
Potwierdzona kontynuacja przez północ (np. start 22:00) Limit długiej sesji liczy się nadal od 22:00. Alert długiej sesji około 02:00 (dla limitu 4h).
Long guard: „Zakończ o HH:mm:ss” Zatrzymanie wpisu następuje na czasie pojawienia się okna (pełna wielokrotność limitu ciągłej sesji). Status końca: 1: FinishByUser.

11. Zmiana zegara systemowego (clock skew)

Anomalie zegara są klasyfikowane przez guard hibernacji/clock-skew i mogą zakończyć aktywne naliczanie statusem AutoFinishDueToClocksDifferences (10). W ścieżce clock-skew nie powstaje dual-entry.

Przypadek Działanie systemu Status końca
Wykryto istotną rozbieżność czasu Zamknięcie aktywnego wpisu na bezpiecznym punkcie i okno informacyjne. 10: AutoFinishDueToClocksDifferences
Brak rozbieżności ponad próg Brak akcji guarda. Brak zmian w WorkTime

12. Zniknięcie aktywnego zadania z API (MissingTask)

Przy odświeżeniu listy zadań API jest źródłem prawdy dla aktywności zadań. Jeśli aktywne lokalnie naliczanie nie znajduje odpowiednika na liście aktywnych zadań z API, system synchronizuje stan przez zatrzymanie lokalnego naliczania i pokazuje komunikat MissingTaskMessage.

Sytuacja Co robi system Uwagi
API zwraca aktywne zadanie zgodne z lokalnym Kontynuacja naliczania; brak okna MissingTask. Normalny przebieg.
API nie zwraca żadnego aktywnego zadania, a lokalnie trwa naliczanie Lokalny wpis jest kończony i pojawia się okno MissingTask. Użytkownik dostaje informację, że zadanie nie jest już dostępne.
API zwraca pustą listę zadań (błąd/niejednoznaczna odpowiedź) Payload jest ignorowany przy aktualizacji cache zadań. Zgodnie z zasadą: puste odpowiedzi API nie powinny niszczyć lokalnego stanu listy.

13. Macierz kompletności przypadków

Obszar Czy powoduje zmianę WorkTime Pokrycie w dokumencie
Hibernacja / sen Tak (stop 1 wpisu lub dual-entry) Sekcje 3, 4, 7
Przejście przez północ Tak (split 23:59:59 / 00:00:00 lub stop) Sekcje 5, 6, 7, 8, 9
Długa ciągła praca Pośrednio (okno decyzji użytkownika) Sekcje 9, 10
Zmiana zegara systemowego Tak (auto-stop status 10) Sekcje 3, 11
Zniknięcie zadania z API Tak (synchronizacyjne zatrzymanie lokalnego naliczania) Sekcja 12
Zdarzenia techniczne (restart/update/sync-korekta) Tak (auto-domknięcia i korekty lokalnego stanu) Sekcja 14
Fallback Quartz przy zmianie doby Tak (awaryjne domknięcie status 3/5) Sekcje 14 (przykłady G, H)

14. Zdarzenia techniczne wpływające na WorkTime (restart/update/sync)

Poniżej lista przypadków, w których czas pracy może zostać zmieniony bez kliknięcia Start/Stop przez użytkownika, a przyczyna nie jest bezpośrednio guardem hibernacji/północy.

Zdarzenie Co robi system Status / efekt
Restart aplikacji z lokalnym aktywnym wpisem bez powiązania serwerowego Wpis jest automatycznie domykany przy starcie aplikacji. 2: AutoFinishAfterAppRestart
Aktualizacja aplikacji / wymuszone domknięcie wszystkich aktywnych wpisów Wszystkie aktywne wpisy są zatrzymywane i synchronizowane. 4: AutoFinishDueToAppUpdate
Wykryte duplikaty aktywnych lokalnych wpisów dla jednego zadania Jeden wpis pozostaje aktywny, pozostałe są technicznie domykane. 4: AutoFinishDueToAppUpdate
Backend sygnalizuje, że lokalnie aktywny wpis nie jest już aktywny po stronie serwera Lokalny wpis zostaje zamknięty/oznaczony jako zmieniony przez backend podczas reverse-sync. Domknięcie lokalne + flaga błędu synchronizacji (HasErrorDuringSync)
Zniknięcie aktywnego zadania z listy API podczas refreshu zadań Synchronizacja zatrzymuje lokalne naliczanie i pokazuje okno MissingTask. Zamknięcie lokalnego wpisu + komunikat dla użytkownika
Watchdog Quartz po północy (>5 minut po 00:00) Awaryjne zamknięcie wpisu na 23:59:59 poprzedniego dnia. 3: AutoFinishAfterSystemRestart
Quartz wykryje długą przerwę aktywności przy zmianie doby Zamknięcie wpisu na znaczniku ostatniej aktywności użytkownika. 5: AutoFinishDueToUserInactivity

Przykład D: Restart aplikacji podczas aktywnej pracy

Krok Skutek
App zamknięta awaryjnie przy aktywnym lokalnym wpisie Po następnym starcie wpis jest domknięty automatycznie.
Domknięty wpis wysyłany do API Końcowy status: 2.

Przykład E: Cleanup duplikatów aktywnych wpisów

Sytuacja Skutek
W bazie lokalnej istnieją 2+ aktywne wpisy jednocześnie Najnowszy pozostaje aktywny, starsze są zamykane technicznie ze statusem 4.

Przykład F: Backend zamknął wpis poza aplikacją

Sytuacja Skutek
Lokalnie wpis aktywny, ale reverse-sync nie znajduje go jako aktywnego po stronie API Wpis lokalny zostaje zamknięty i oznaczony jako nadpisany przez backend.

Przykład G: Quartz watchdog po północy (fallback bezpieczeństwa)

Sytuacja Skutek
Po 00:05 nadal istnieje aktywny wpis, który nie został zamknięty przez midnight/hibernation guard Awaryjne zamknięcie na 23:59:59 poprzedniego dnia ze statusem 3.

Przykład H: Quartz + długa przerwa przy zmianie dnia

Sytuacja Skutek
Wykryta przerwa aktywności > 20 min podczas przetwarzania zmiany doby Zamknięcie wpisu na czasie ostatniej aktywności ze statusem 5.