Skip to content

Naprawy/audyt 2026 09 23 - #4

Merged
mbeczynski merged 3 commits into
mainfrom
naprawy/audyt-2026-09-23
Sep 23, 2026
Merged

mbeczynski merged 3 commits into
mainfrom
naprawy/audyt-2026-09-23

Conversation

@mbeczynski

Copy link
Copy Markdown
Contributor

No description provided.

mbeczynski and others added 3 commits September 23, 2026 12:58
The queue read goes through `rclone rc vfs/stats`. When it timed out the
caller got nil, substituted zeros for every counter, and `UploadState.from`
concluded `.upToDate` — so both `drive-status` and the app card announced
"Wszystko wysłane na Google Drive" while 386 bands were still waiting. It
happened twice on 23 September, and `drive-status` printed the contradiction
in plain sight: "Kolejka wysylki: (interfejs rc nieosiagalny)" two lines above
the all-clear.

Missing numbers are not zeros. `UploadState` gains `.queueUnknown`, which is
not nominal, so the menu bar cannot go green on it; `from(...)` now takes a
`queueKnown:` argument with no default, which forces every call site to answer
the question it used to skip. `BufferStatus` defaults it to false, so an
unpopulated status reads as unmeasured rather than empty.

The three-state verdict no longer fits two booleans, so the badge moves onto
`UploadState` itself, and the views stop claiming "Brak zaległości" from
counters nobody read.

Also raises the read timeout from 30 s to 60 s: the same call was measured at
36.7 s under a saturated buffer, and at 0.03 s a second later. That alone would
not have fixed anything — a slower answer is still no answer — but it makes the
question worth asking.

Verified on a live bad sample: with the mount up and rclone refusing to answer,
the new binary prints "Nie wiadomo, co czeka w kolejce".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…wyslane"

`CMLock` bylo MARTWYM KODEM - 241 linii mechanizmu blokad, ktorego nie wolalo
ani jedno miejsce w repozytorium, takze zaden test. `attach()`, `detach()`,
`verify()` i `create()` nie mialy wiec zadnego wzajemnego wykluczenia, a
`detach()` ma okno ~10,5 min (sleep 35 s + drenaz do 600 s), w ktore tik
`gdrive-attach` (StartInterval 900) trafia z duzym prawdopodobienstwem: widzi
`.detached`, podpina obraz z powrotem, Time Machine rusza z zapisem, a
`prepare-shutdown` konczy drenaz i wypisuje "Mozna restartowac". Teraz te
cztery operacje ida przez `withCMLock`, a nieudane zdobycie blokady zwraca
`busyResult`, nie milczacy sukces. `acquire()` weryfikuje tez zapis PID-a na
sciezce "katalog utworzony" - dotad robila to tylko sciezka przejecia, wiec
proces przerwany miedzy `createDirectory` a `writePid()` byl uznawany za
osierocony i blokada trafiala do DWOCH wlascicieli naraz.

"Nie wiem" czytane jako dobra odpowiedz, cztery razy:
- `isQuiet` ignorowalo `erroredFiles`. Pasma porzucone przez rclone wypadaja
  z kolejki, wiec `detach()` meldowal "wszystko wyslane na Google Drive", a
  `safeToRebootNow()` mowil TAK - przy danych istniejacych tylko lokalnie.
  Czekanie chodzi teraz po `isIdle`, werdykt po `isQuiet`: gdyby czekac na
  `isQuiet`, kazde podpiecie placiloby pelne 600 s, bo porzucone pasma zostaja
  w liczniku do konca zycia procesu rclone.
- `vfs/stats` bez sekcji `diskCache` dawalo komplet ZER zamiast `nil`, bo
  fallback `?? json` plus `number()` zwracajace 0 dla brakujacego klucza
  przechodzily walidacje. `queueKnown` bylo wtedy prawda i znow padalo
  "Wszystko wyslane". To ten sam wzorzec, ktory naprawiono wyzej przez
  `queueUnknown`, tylko przesuniety do parsowania - ktore siedzialo w funkcji
  async i nie dalo sie przetestowac. Wydzielone i pokryte testem.
- `ImageProbe` polykalo bledy LISTOWANIA (`try? regularFiles()`), wiec ENXIO
  na samym katalogu dawalo `.nothingToProbe`, a `attachment` mapowalo to na
  `.attached`. Martwy obraz uchodzil za zywy - awaria z 22.09 wejsciem, ktorego
  tamta poprawka nie objela.
- `mountPoints()` i `mountTable()` szly przez `/sbin/mount` BEZ limitu czasu,
  czyli pytaly o wolumen, ktory bywa martwy, tym sposobem, ktory na nim wisi.
  Zamiast ograniczac ten tryb awarii - usuniety: `getmntinfo(MNT_NOWAIT)`
  czyta tablice z pamieci jadra, bez procesu i bez dotykania systemu plikow.
  `MNT_WAIT` wolalby `VFS_STATFS` na kazdym montowaniu i wieszalby sie tak samo.

`create()` sprawdzalo istnienie obrazu przez `fileExists` na cache FUSE i - w
przeciwienstwie do `attach-image` - nie czekalo na `BufferReadiness`. W oknie,
gdy rclone wystawil montowanie przed wczytaniem katalogu, straznik "obraz juz
istnieje" przepuszczal, a `hdiutil create` szedl na sciezke istniejacego
backupu, z piecioma ponowieniami. Teraz nieobecnosc potwierdza `rclone lsf` na
zdalnym, a BRAK ODPOWIEDZI PRZERYWA - operacja nieodwracalna nie moze isc
naprzod bez pewnosci.

`verify()` meldowalo "Obraz NIESPOJNY", gdy `fsck_apfs` padl, bo tik agenta
wyrwal mu urzadzenie - falszywy alarm o utracie backupu. `attach-image`
rozroznia teraz sukces / pominiecie (kod 0) / awarie (kod 1) po typie
`CMActionResult.Disposition`, a nie po tekscie: "zajete przez inna operacje"
nie jest awaria i nie ma prawa zapisywac sie jako awaria w logu bledow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ma wykryc

`ProcessRunner.run` ma `timeout` domyslnie `nil`, a `TimeMachineStatus` wolalo
`tmutil` BEZ limitu we wszystkich siedmiu miejscach. Przy martwym montowaniu
FUSE-T (incydent ENXIO z 22.09) `tmutil destinationinfo` blokuje sie
w nieprzerywalnym I/O, `BackupHealth.currentReport` nigdy nie wraca, a launchd
ze `StartInterval` NIE odpala drugiej instancji, dopoki zyje pierwsza - czujka
milkla NA STALE. Naglowek `BackupHealth.swift` deklarowal dokladnie odwrotnosc
tego stanu i zostal poprawiony.

Limit 90 s na wspolnym wejsciu, dobrany NIE pod "ile zwykle trwa" (ulamek
sekundy), tylko z zapasem dwoch rzedow wielkosci - bo 120 s dobrane pod czysty
start juz raz zabraklo o 10 s po awarii. Realne maksimum czekania to 100 s
(SIGTERM -> SIGKILL -> poddanie sie) przy oknie czujki 1800 s. Brak odpowiedzi
nie jest juz `false`: doszly `runningState() -> Bool?` i `destinationReading()`,
a `nil` daje OSOBNY problem "tmutil nie odpowiada - nie wiadomo, gdzie idzie
backup", a nie "Time Machine nie wskazuje na CloudMachine" (ten drugi
wysylalby czlowieka do przestawiania celu, ktory jest ustawiony dobrze).

Dozorca uznawal pauze za wykonana, nie patrzac na wynik `tmutil stopbackup`
(`_ = try? await ...`, a przejscie stanu BEZWARUNKOWE). Gdy polecenie padlo
- brak uprawnien albo przekroczony limit - stan szedl na `.pausedForBuffer`,
a `stopBackup()` wola sie tylko przy ZMIANIE stanu, wiec nigdy nie ponawial.
Time Machine pisal dalej, dozorca czekal na drenaz, dysk zapelnial sie do
konca, a w logu stalo "PAUZA ... czekam na wysylke".

`.pausedForQuota` (brak miejsca na Google Drive) dzielila galaz wznowienia
z `.pausedForBuffer`, a warunek patrzyl na bufor i wolne miejsce LOKALNEGO
dysku - o miejscu na Dysku nie wiedzial nic. `hitStorageQuota()` czyta tylko
30 minut logu rclone, wiec po zatrzymaniu Time Machine wpisy sie starzaly
i pauza spadala sama. Wyjscie wymaga teraz POZYTYWNEGO dowodu z `remoteQuota()`;
brak odpowiedzi PODTRZYMUJE pauze.

`freeGB()` zwracalo 0 przy nieudanym `statfs`. Warunek pauzy byl wtedy
spelniony natychmiast, a warunek wznowienia NIGDY - dozorca wstrzymywal Time
Machine i nie wznawial go juz nigdy. Teraz `Int?`; osobny test pilnuje, ze
ZMIERZONE zero nadal pauzuje, zeby ta poprawka nie przeszla w druga skrajnosc.

Panel nie pytal ANI RAZU o date ostatniej UDANEJ kopii - `grep BackupHealth`
po `CloudMachineApp/` nie dawal zadnego trafienia. Montowanie stoi, obraz
podpiety, kolejka pusta, Time Machine od dwoch dni nie dokonczyl kopii -
i swiecilo "Sprawny / Gotowe". To jest awaria opisana w naglowku
`BackupHealth.swift` jako najgrozniejsza w tym systemie.

Nieudane `osascript` bylo wyrzucane przez `_ = try?`, a `report()` i tak
zapisywalo `lastSummary` i `lastAlertAt` - alarm ginal po cichu na 12 h.
Przypomnienie i tak nie dzialalo, bo `summary` zawiera wiek awarii ("od 3 h"),
wiec zmienialo sie CO GODZINE i powiadomienie wracalo przy kazdym przebiegu.
`connect` chodzilo na rclone z Homebrew, ktorego wg README na tej maszynie nie
ma, podczas gdy `isConfigured` w tym samym pliku idzie przez wlasna binarke.

Po dolozeniu `Attachment.unknown` wyrazenie `attached: attachment != .detached`
zaczelo dawac `true` - czujka MILCZALABY o stanie, ktorego nie zna. `mounted`
i `attached` sa teraz trojstanowe, a brak odczytu daje wlasny problem.

ZNANY OSAD, rozstrzygniety swiadomie: `CloudMachineController.swift:124` nadal
czyta `isMounted`, wiec panel przy nieodczytanej tablicy montowan pokaze
"Bufor nie dziala". Po przebudowie na `getmntinfo` ten odczyt idzie z pamieci
jadra i praktycznie nie ma jak zawiesc, skutkiem jest falszywy ALARM (nie
cisza), a panel odswieza sie co 10 s. Trojstanowosc wymagalaby zmiany
`UploadState.from(mounted:)` i dwunastu testow kodujacych dzisiejszy kontrakt
- nie warte tego dla galezi, do ktorej realnie nie da sie dojsc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mbeczynski
mbeczynski merged commit 1eb0a13 into main Sep 23, 2026
0 of 2 checks passed
@mbeczynski
mbeczynski deleted the naprawy/audyt-2026-09-23 branch September 23, 2026 15:34
mbeczynski added a commit that referenced this pull request Sep 25, 2026
CI CloudMachine pada od scalenia PR #4 (2026-09-23); ostatni zielony przebieg
to 2026-09-22. Przyczyna: `swift format lint --strict` zglasza piec linii
dluzszych niz limit.

To NIE jest zmiana zachowania - wylacznie zawijanie wierszy. Zadna instrukcja
nie zostala dodana ani usunieta.

DLACZEGO TO WAZNIEJSZE NIZ PIEC WIERSZY: przez dwa dni zadna zmiana w tym
repozytorium nie byla sprawdzana przez build ani testy, bo sygnal juz swiecil
na czerwono z innego powodu. Kolejna wada wjechalaby na main bez ostrzezenia,
a nikt nie odroznilby nowej porazki od tej starej.

Zmierzone tym samym poleceniem, ktorego uzywa CI: przed - piec bledow
`[LineLength]`, po - zero. `swift build` i `swift test` przechodza: 224 testy,
0 porazek.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQC6qZ3rTwghioqdpLDckT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant