◆ wytłumaczone od zera, bez żargonu

Jak naprawdę działa Privio

Z czego Privio jest zbudowane i dlaczego akurat tak, co się z czym łączy, co dzieje się krok po kroku przy każdej funkcji i do jakich części systemu macOS aplikacja się odwołuje.

01 - Na początek

Co to właściwie jest

Privio to strażnik prywatności dla Maca. Pilnuje wybranych aplikacji, stron i plików - a otwiera je dopiero, gdy potwierdzisz, że to Ty (odciskiem palca albo hasłem Maca).

Wyobraź sobie portiera w budynku. Nie zna Twojego hasła i nie ma kluczy do Twojego mieszkania - ale stoi przy drzwiach i wpuszcza tylko wtedy, gdy ochrona (system macOS) potwierdzi, że to naprawdę Ty. Privio jest właśnie takim portierem: sam z siebie niczego nie „wie” o Twoich sekretach, tylko prosi zaufane części systemu, żeby Cię rozpoznały.

Ma pięć zadań (modułów), które poznasz dalej: chowanie aplikacji, blokowanie stron WWW, „zasłonę prywatności” na ekran, szyfrowany sejf na pliki i blokadę Maca, gdy odejdziesz. Wszystko dzieje się na Twoim komputerze - nic nie jest wysyłane do internetu.

◆ Jedna złota zasadaPrivio nigdy nie zna Twojego hasła ani odcisku. Zawsze prosi o to system macOS i specjalny chip bezpieczeństwa, a w odpowiedzi dostaje wyłącznie „tak” albo „nie”. Nie da się z Privio wykraść czegoś, czego ono po prostu nie ma.
02 - Materiały budowlane

Z czego jest zbudowane - i dlaczego akurat to

Każdy klocek Privio wybrano z konkretnego powodu. Oto najważniejsze technologie prostym językiem: co to jest, po co i dlaczego to, a nie coś innego.

03 - Połączenia

Co się z czym łączy

Privio prawie nic nie robi „samo”. Zamiast tego rozmawia z zaufanymi częściami systemu macOS - każda odpowiada za co innego. Kliknij element, żeby zobaczyć, o co Privio go prosi.

↑ kliknij dowolny element wokół Privio
◆ Dlaczego takZamiast pisać własne (i ryzykowne) rozwiązania, Privio korzysta z gotowych, sprawdzonych mechanizmów Apple. To bezpieczniejsze, szybsze i nie wymaga od Ciebie nadawania aplikacji podejrzanych, szerokich uprawnień.
04 - Film w zwolnionym tempie

Co się dzieje krok po kroku

Wybierz sytuację, a rozłożę ją na czynniki pierwsze - łącznie z tym, którą część systemu Privio wtedy angażuje (fioletowe znaczniki).

◆ A jeśli masz Apple Watch?Wszędzie tam, gdzie pojawia się okno Touch ID, to samo okno możesz zatwierdzić dwuklikiem bocznego przycisku Apple Watch - o ile dana apka lub strona jest ustawiona na „Touch ID lub hasło Maca” (a sejf zawsze). Przy trybie „Tylko Touch ID” zegarek nie zadziała - to celowe ograniczenie systemu. Szczegóły w rozdziale 10.
05 - Pod maską

Jak działają mechanizmy w środku

Kilka pytań, które prędzej czy później się pojawiają - z uczciwymi odpowiedziami, także o granicach tego, co w ogóle da się zrobić na Macu.

06 - Warsztat

Warsztat: jak w ogóle powstaje taka apka

Od tego miejsca schodzimy głębiej - z każdym rozdziałem coraz bardziej „pod maskę”, aż zobaczysz dokładnie, jak Privio jest zrobione w środku. To część dla ciekawskich: nie musisz jej czytać, żeby korzystać z aplikacji.

◆ Jak czytać dalszą częśćNie musisz rozumieć wszystkiego za pierwszym razem. Traktuj to jak zwiedzanie fabryki: najpierw hala i maszyny (ten rozdział), potem coraz drobniejsze tryby. Fragmenty kodu są prawdziwe (albo uproszczone - wtedy jest to zaznaczone) i możesz je po prostu oglądać jak ilustracje.

Czego się używa

🛠️

Xcode

darmowe od Apple

Program, w którym pisze się i buduje aplikacje na Maca/iPhone’a. Zawiera edytor kodu, kompilator (zamienia kod na działający program) i symulatory.

📄

project.yml + XcodeGen

projekt jako plik tekstowy

Zamiast klikać ustawienia projektu myszką, Privio opisuje je w zwykłym pliku tekstowym i generuje projekt komendą. Dzięki temu ustawienia da się śledzić w git i nikt niczego przypadkiem nie „odklika”.

Z czego składa się projekt: dwa „produkty” (targety)

Target to jedna rzecz, którą kompilator ma wyprodukować. Privio ma dwa - i to jest fundament całej reszty przewodnika:

🧠

PrivioCore

framework · biblioteka-mózg

Cała logika: co jest zablokowane, kiedy pytać o odcisk, jak zapisać ustawienia. Nie ma tu ani jednego przycisku - dzięki temu da się to testować automatycznie, bez klikania.

🎨

Privio

aplikacja · to, co widać

Okna, sidebar, ikona w pasku menu. Ta część „importuje” mózg (PrivioCore) i pokazuje jego stan na ekranie. Sama nie podejmuje decyzji bezpieczeństwa.

tak buduje się i uruchamia projekt (z dokumentacji projektu)

# Wygeneruj projekt Xcode z pliku tekstowego project.yml
xcodegen generate

# Zbuduj aplikację
xcodebuild -project Privio.xcodeproj -scheme Privio build

# Uruchom testy „mózgu” (PrivioCore) - bez klikania
xcodebuild -project Privio.xcodeproj -scheme PrivioCoreTests test
◆ Ważne pojęcieGotowe Privio, które pobierasz, jest podpisane certyfikatem Apple (Developer ID) i notaryzowane - Apple potwierdza, kto jest autorem i że nikt nie podmienił plików. Dzięki temu macOS uruchamia aplikację bez ostrzeżeń, mimo że pochodzi spoza App Store (więcej w rozdziale 10).
07 - Język Swift

Swift w pigułce: klocki, z których zbudowany jest kod

Żeby czytać kod Privio, wystarczy poznać garść pojęć. Nie musisz umieć ich pisać - wystarczy je rozpoznawać.

Stałe, zmienne i typy

let to wartość, która się nie zmienia (jak napis na tabliczce), var to taka, którą można zmieniać. Każda wartość ma typ - czym jest: tekstem (String), liczbą (Int), prawdą/fałszem (Bool), datą (Date) czy unikalnym identyfikatorem (UUID).

Trzy sposoby na „paczkę danych”

📦

struct

kopiowana paczka

Zestaw pól trzymanych razem. Przy przekazaniu jest kopiowany - dostajesz własny egzemplarz. Idealny na dane, np. opis chronionej apki.

🔗

class

współdzielony obiekt

Podobny, ale dzielony przez odniesienie - kilka miejsc może wskazywać ten sam obiekt. Używany, gdy coś ma „żyć” i być wspólne, np. kontroler.

🎚️

enum

zamknięta lista wariantów

Skończony wybór możliwości - i każdy wariant może nieść dane. Kompilator dopilnuje, że obsłużysz każdy przypadek. Świetne na „stany” i „akcje”.

Sources/PrivioCore/Models/ProtectedApp.swift - prawdziwa paczka danych (struct)

public struct ProtectedApp {
    public var bundleIdentifier: String     // stały identyfikator apki
    public var displayName: String          // nazwa pokazywana użytkownikowi
    public var protectionEnabled: Bool      // czy chronimy tę apkę

    // Wartość opcjonalna: „liczba sekund ALBO nic”.
    public var lockAfterInactivity: TimeInterval?
}

Opcjonalne wartości: „coś albo nic” (znak ?)

Znak zapytania po typie oznacza, że wartości może w ogóle nie być (po polsku „nic” to nil). To bardzo ważny pomysł: Swift zmusza programistę, żeby obsłużył przypadek pustki - nie da się przez przypadek użyć „niczego” jak zwykłej liczby. Stąd w kodzie Privio tyle znaków ?: to jawne „uwaga, tu może nic nie być”.

Enum, który niesie dane

Sources/PrivioApp/App/AppModel.swift - lista akcji wymagających autoryzacji

enum SensitiveAction {
    case disableProtection            // wariant bez danych
    case removeApp(name: String)      // wariant, który niesie nazwę apki
    case pauseWebsiteProtection(minutes: Int)
}

Dzięki temu „usuń aplikację X z ochrony” to jedna, sprawdzana rzecz - a nie luźny tekst, w którym łatwo o literówkę.

Domknięcia: funkcja podana jak wartość

Czasem chcemy przekazać „co ma się stać później”. W Swift robi się to domknięciem (closure) - kawałkiem kodu w klamrach, który można gdzieś podać i wywołać, gdy przyjdzie czas. Privio łączy tak moduły:

Sources/PrivioApp/App/PrivioApp.swift - „gdy trzeba zablokować, zrób to”

// proximity dostaje przepis na akcję; wykona go, gdy odejdziesz od Maca
proximity.onLockProtectedApps = { model.lockAllForProximity() }
08 - Współbieżność

Jak apka robi kilka rzeczy naraz - i nie plącze się

Prawdziwy problem: w tej samej chwili może przyjść kilka zdarzeń (włączyłeś apkę, minął minutnik, ekran się zablokował). Gdyby wszystkie naraz grzebały w stanie blokad, powstałby chaos. Oto jak Swift temu zapobiega.

async / await: poczekaj, nie zamrażając

Niektóre rzeczy trwają (np. czekanie na odcisk). Słowo async oznacza „ta funkcja może poczekać”, a await - „tu czekamy na wynik”. W trakcie czekania apka nie zamarza - dalej reaguje na Ciebie. To najważniejszy nowoczesny wzorzec w całym kodzie.

Task: uruchom pracę w tle

Task { ... } to „zrób to obok, nie blokując reszty”. Tak Privio nasłuchuje strumienia zmian stanu albo odlicza minutniki bezczynności (lekkie „uśpienie” zamiast ciągłego sprawdzania).

actor: strażnik jednego stanu

To sedno. actor wygląda jak zwykły obiekt, ale ma supermoc: obsługuje tylko jedno zgłoszenie na raz. Nawet jeśli pięć zdarzeń przyjdzie równocześnie, ustawią się w kolejce - więc stan blokad nigdy się nie „rozjedzie”. Cały mózg Privio to jeden taki strażnik:

Sources/PrivioCore/Enforcement/InProcessEnforcementService.swift

// „actor” = obiekt, który pilnuje swojego stanu; obsługuje po kolei
public actor InProcessEnforcementService: EnforcementControlling {
    private var state: EnforcementState            // cały ulotny stan tutaj
    private var subscribers: [UUID: AsyncStream<EnforcementState>.Continuation] = [:]
    // ...monitor, kontroler apek, uwierzytelnianie - wstrzyknięte z zewnątrz
}

AsyncStream: strumień „migawek” do UI

Gdy stan się zmienia, strażnik nie „woła” UI. Zamiast tego nadaje kolejne migawki całego stanu, a UI je łapie w pętli for await i przerysowuje się. To jak transmisja: nadawca nie wie, kto ogląda - po prostu wysyła kolejne klatki.

◆ Dlaczego wszystko jest „przesyłalne”Migawki stanu są oznaczone jako Sendable (bezpieczne do przekazania między wątkami) i Codable (dają się zapisać jak zwykły tekst/JSON). To nie przypadek: dzięki temu ten sam mózg można kiedyś przenieść do osobnego, lepiej chronionego procesu, a UI nawet tego nie zauważy - o tym w następnym rozdziale.
09 - Architektura

Architektura: jak jedna granica trzyma to wszystko w ryzach

Teraz połączmy kropki. Cała siła Privio bierze się z jednej zasady: UI i mózg rozmawiają wyłącznie przez wąską, ściśle określoną granicę - „szew” (seam).

1. Kontrakt (protokół)

Najpierw spisujemy, czego UI może chcieć od mózgu - jako listę obietnic, bez wnikania, jak są spełniane:

Sources/PrivioCore/Enforcement/EnforcementControlling.swift

// Umowa: „ktokolwiek to zrobi, udostępni te działania”
public protocol EnforcementControlling: AnyObject, Sendable {
    func stateUpdates() async -> AsyncStream<EnforcementState>   // strumień migawek
    func authenticate(appID: UUID, preferPassword: Bool) async -> Bool
    func lock(appID: UUID, reason: ActivityEvent.Reason) async
}

2. Wykonawca (actor), który dostaje współpracowników z zewnątrz

Konkretna implementacja to nasz strażnik. Co ważne, on sam nie tworzy sobie czujnika odcisku ani monitora apek - dostaje je z zewnątrz (to się nazywa wstrzykiwanie zależności). Dzięki temu w testach można podać atrapy, a w trybie podglądu - nic.

szkic uproszczony na podstawie InProcessEnforcementService.init

InProcessEnforcementService(
    store: ConfigStore(),               // zapis ustawień na dysk
    monitor: WorkspaceAppMonitor(),     // słucha, która apka wyszła na wierzch
    controller: WorkspaceAppController(),// chowa/pokazuje apki
    authenticator: LocalAuthenticator() // Touch ID / hasło
)
// w testach zamiast tych „prawdziwych” podaje się atrapy - i logikę
// da się sprawdzić bez czytnika odcisku i bez klikania

3. Reaktywna pętla

UI ma jeden łącznik z mózgiem - AppModel - oznaczony jako @Observable (czyli „gdy się zmienię, odśwież widoki”). Cała pętla wygląda tak:

1
Klikasz coś w UI → widok woła metodę na AppModel.
2
AppModel przekazuje to przez granicę: await service.authenticate(...).
3
Strażnik (actor) robi swoje i zmienia swój stan.
4
Nadaje nową migawkę strumieniem; AppModel łapie ją pętlą for await.
5
Bo AppModel jest @Observable, SwiftUI sam przerysowuje ekran. Zero ręcznego „odśwież”.
◆ Po co ten cały wysiłekTrzy zyski: (1) mózg jest w pełni testowalny bez UI; (2) UI nie potrafi obejść zabezpieczeń, bo zna tylko kontrakt; (3) implementację mózgu można podmienić (dziś w tym samym procesie, jutro w osobnym), a reszta apki tego nie zauważy. To jest dokładnie ten rodzaj decyzji, który odróżnia „apkę-prototyp” od „apki, którą da się utrzymywać latami”.
10 - Bezpieczeństwo

Bezpieczeństwo od środka

Tu jest sedno całego produktu. Zasada przewodnia: Privio ma jak najmniej wiedzieć i jak najmniej przechowywać - żeby nie było czego ukraść. Rozwiń, co Cię ciekawi.

Touch ID: Privio prosi, system i chip odpowiadają

Do sprawdzenia tożsamości służy systemowy mechanizm (LocalAuthentication). Privio tworzy „prośbę o potwierdzenie” i czeka na odpowiedź tak/nie. Obsługiwane warianty: sam Touch ID, Touch ID lub hasło Maca, albo samo hasło. Odcisk i hasło sprawdza osobny chip - Secure Enclave - do którego Privio (ani nawet reszta systemu) nie zagląda.

szkic uproszczony - tak wygląda prośba o potwierdzenie

let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthentication,
                       localizedReason: "odblokować aplikację") { ok, _ in
    // dostajemy tylko ok = prawda/fałsz; nigdy odcisku ani hasła
}
Dowód obecności: klucz w Secure Enclave podpisuje wyzwanie

Samo „tak” można teoretycznie podrobić. Dla pewności Privio prosi bezpieczny chip, by podpisał losowe wyzwanie kluczem, który fizycznie nie może opuścić tego chipa. Poprawny podpis = dowód, że to naprawdę ten Mac i ten właściciel. To ten sam rodzaj mechanizmu, co przy płatnościach zbliżeniowych.

Pęk kluczy: sekrety zamknięte „na obecność”

Sekrety (np. klucz do sejfu) trafiają do systemowego Pęku kluczy z regułą: „wydaj dopiero po potwierdzeniu obecności właściciela” i „nigdy nie wynoś z tego Maca”. Lokalne buildy bywają zbyt „surowe”, by system oddał taki sekret - dlatego jest opisany, awaryjny plan B. Privio nigdy nie zapisuje hasła Twojego konta macOS.

Prywatny sejf: gotowe szyfrowanie Apple, nie domowej roboty

Sejf to zaszyfrowany obraz dysku tworzony systemowym narzędziem hdiutil (szyfrowanie AES-256). Hasło (256-bitowy klucz) podawane jest narzędziu „na ucho” - przez wejście standardowe - nigdy jako widoczny argument ani w logach. Plik sejfu leży w osobnym folderze, więc odinstalowanie Privio nie kasuje Twoich danych. Jest też jednorazowy klucz odzyskiwania na wypadek utraty dostępu.

idea - klucz idzie do narzędzia bezpiecznym kanałem, nie w komendzie

// AES-256, hasło podane przez stdin (nie w argumentach procesu)
hdiutil create -encryption AES-256 -stdinpass ... sejf.sparsebundle
Dlaczego apka jest poza App Store (i czemu to bezpieczne)

Żeby zarządzać innymi aplikacjami, system wymaga, by Privio nie działało w „piaskownicy” - ciasnym pudełku, w którym apki ze sklepu są odizolowane. Dlatego dystrybucja jest poza App Store. W zamian aplikacja jest podpisana certyfikatem Developer ID i notaryzowana - Apple potwierdza autora i to, że pliku nikt nie podmienił. Aktualizacje idą przez sprawdzony mechanizm (Sparkle), z podpisem każdej paczki.

Apple Watch: kiedy odblokuje aplikację lub stronę, a kiedy nie

To nie Privio „obsługuje” Apple Watch - Privio odpala systemową politykę zależną od wybranego trybu ochrony, a Apple Watch działa tylko z tą, która dopuszcza także hasło. Dlatego dla aplikacji i stron zależy to od Twojego ustawienia:

Tryb ochronyApple Watch odblokuje?
Tylko Touch ID❌ Nie - tylko fizyczny czytnik (Privio chowa opcję hasła, a systemu nie da się zatwierdzić zegarkiem, gdy prompt jest wyłącznie biometryczny)
Touch ID lub hasło Maca✅ Tak - to „miękka” polityka; zatwierdzasz dwuklikiem bocznego przycisku zegarka
Tylko hasło❌ Nie - wpisujesz hasło Maca

Prywatny sejf zawsze używa trybu „biometria lub hasło”, więc Apple Watch otworzy go zawsze. Warunek dla wszystkich przypadków: zegarek na ręce, odblokowany, na tym samym koncie Apple i włączony w Ustawieniach systemowych.

11 - System macOS

Rozmowa z systemem: konkretne „drzwi”, z których korzysta Privio

Najgłębszy poziom: nazwy prawdziwych mechanizmów macOS, których Privio używa - i sprytne sztuczki, dzięki którym nie potrzebuje inwazyjnych uprawnień.

Wykrywanie apek bez ciągłego sprawdzania (NSWorkspace)

System ma „tablicę ogłoszeń” zdarzeń. Privio zapisuje się na powiadomienia o aktywacji, uruchomieniu i zamknięciu apek - i dostaje sygnał tylko wtedy, gdy coś się dzieje. Zero marnowania baterii na ciągłe odpytywanie.

Sources/PrivioCore/Services/AppMonitor.swift

let center = NSWorkspace.shared.notificationCenter
// „powiadom mnie, gdy jakaś apka wyjdzie na wierzch”
observe(NSWorkspace.didActivateApplicationNotification)  { .activated($0) }
observe(NSWorkspace.didLaunchApplicationNotification)    { .launched($0) }
observe(NSWorkspace.didTerminateApplicationNotification) { .terminated($0) }
Chowanie i przywracanie okien (NSRunningApplication)

Mając identyfikator apki, Privio prosi system, by ją schował (hide()) albo przywrócił (activate()). Takie zmiany muszą iść przez „główny wątek” interfejsu - to reguła AppKit, o której trzeba pamiętać, inaczej apka się wywali.

Zasłona prywatności bez prawa do nagrywania ekranu (CGWindowList)

Żeby nałożyć zasłonę tylko na okna chronionej apki, Privio pyta system o położenie i rozmiar okien - ale świadomie nie o ich obraz. Dzięki temu nie potrzebuje uprawnienia do nagrywania ekranu. Trzeba tylko przeliczyć współrzędne, bo system liczy je od góry, a interfejs - od dołu.

Wyczuwanie urządzenia i baterii (IOBluetooth)

Przez publiczny interfejs Bluetooth Privio odczytuje siłę sygnału zaufanego urządzenia (i, jeśli się da, poziom baterii), by wykryć oddalenie. Bez nowych uprawnień. Wartości zależą od sprzętu, więc trzeba je sprawdzać na prawdziwych urządzeniach - w kodzie jest nawet dodatkowy „latarnik” na zegarek, bo zwykły sygnał bywa zawodny.

Natychmiastowa blokada ekranu (świadomy wyjątek)

Przy odejściu od Maca liczy się natychmiastowość. Zwykłe, publiczne sposoby na nowszym macOS albo zniknęły, albo blokują dopiero po opóźnieniu. Dlatego Privio używa bezpośredniej komendy blokady z systemowej biblioteki (SACLockScreenImmediate) - to jedyny świadomy wyjątek od zasady „tylko publiczne API”, opisany w kodzie, bez żadnych dodatkowych uprawnień. Gdyby ta komenda zniknęła, jest kilka zapasowych sposobów po kolei.

Blokada stron: lokalne proxy + plik PAC + wtyczka

Privio uruchamia lokalne proxy (na 127.0.0.1) i mówi systemowi, przez plik reguł PAC, żeby kierował do niego tylko chronione domeny (reszta internetu idzie bezpośrednio). Gdy strona jest zablokowana, proxy odmawia połączenia i prosi o Touch ID. W przeglądarkach Chromium dodatkowo działa wtyczka, która pokazuje ekran blokady wprost w karcie. Stary sposób z plikiem hosts nie jest już używany.

Jedna pułapka: Google i część stron łączą się przez QUIC (HTTP/3 po UDP), którego zwykłe proxy nie widzi - dlatego, gdy ochrona stron jest włączona, Privio blokuje QUIC systemową zaporą, żeby przeglądarka spadła na połączenie, które da się wyegzekwować. Dzięki temu blokada działa też w Safari i Firefoksie, nie tylko w Chromium. Pełną, w 100% niezależną od przeglądarki wersję da dopiero systemowy filtr sieciowy (NetworkExtension).

12 - Anatomia startu

Anatomia startu: co się składa w całość przy uruchomieniu

Do tej pory patrzyliśmy na klocki osobno. Teraz zobaczmy moment, w którym wszystkie są łączone w jednym miejscu - to tu widać, jak apka jest naprawdę „zmontowana”.

Cała aplikacja ma jeden konstruktor (PrivioApp.init), który wykonuje starannie ułożoną kolejność kroków. Kolejność nie jest przypadkowa - każdy krok zależy od poprzedniego:

1
Rejestracja krojów pisma (Nunito + Font Awesome) - MUSI być pierwsza, zanim jakikolwiek widok policzy szerokość tekstu czy ikony.
2
Ustawienie języka - zanim cokolwiek wczyta napisy, bo inaczej część tekstów byłaby w złym języku.
3
Dostęp do dysku i wczytanie stanu - ConfigStore czyta konfigurację, a stan startowy ładuje się z wszystkim jako zablokowane.
4
Wykrycie trybu podglądu - jeśli apka jest uruchomiona tylko po to, by zrobić „zdjęcie” interfejsu, NIE podłącza monitorów ani chowania apek.
5
Zbudowanie mózgu - powstaje aktor egzekwujący, któremu wstrzykuje się współpracowników (monitor apek, kontroler okien, uwierzytelnianie, minutniki, proxy). W trybie podglądu: same puste miejsca.
6
Kontrolery i spięcie - powstają sejf, AppModel i moduł „po odejściu”, a między nimi wpina się „przepisy na akcje” (domknięcia).
7
Scena - okno SwiftUI dostaje gotowe kontrolery przez środowisko (.environment) i zaczyna renderować.

Sources/PrivioApp/App/PrivioApp.swift - skrócony montaż (init)

init() {
    PrivioFonts.registerAll()            // fonty gotowe, zanim UI policzy tekst
    AppLanguage.applyStartupOverride()   // język ustawiony przed wczytaniem napisów

    let store = ConfigStore()
    let initialState = EnforcementState.loaded(from: store)   // wszystko .locked
    let isSnapshot = ProcessInfo.processInfo.environment["PRIVIO_SNAPSHOT"] != nil

    // Mózg dostaje współpracowników z zewnątrz (w trybie podglądu: nil)
    let service = InProcessEnforcementService(
        store: store,
        monitor:       isSnapshot ? nil : WorkspaceAppMonitor(),
        controller:    isSnapshot ? nil : WorkspaceAppController(),
        authenticator: isSnapshot ? nil : LocalAuthenticator(),
        scheduler:     isSnapshot ? nil : RealInactivityScheduler(),
        webProxy:      isSnapshot ? nil : WebProxyServer(),
        initialState: initialState)

    let model = AppModel(service: service, initialState: initialState, catalog: catalog)
    // ...dalej: VaultController, ProximityController, spięcie domknięciami, scena
}
◆ Dlaczego to ważneTo jedno miejsce („kompozycja”) decyduje, z czego apka jest złożona. Wszystko inne dostaje gotowe zależności i nie musi wiedzieć, skąd pochodzą. Dzięki temu podmiana jednego klocka (np. na atrapę w teście) nie dotyka reszty.
13 - Trwałość danych

Trwałość i integralność: co przetrwa restart, a co znika

Wiemy już, że status blokad jest ulotny. Zejdźmy głębiej: jak dokładnie zapisywane są ustawienia, jak apka radzi sobie ze starszymi plikami i jak wykrywa, że ktoś przy pliku majstrował.

Gdzie i co się zapisuje

Ustawienia trafiają jako zwykły plik JSON do ~/Library/Application Support/Privio/config.json. Zapisywana jest tylko konfiguracja (które apki, jakie timeouty, ustawienia modułów) - nigdy sekrety. Klucze i hasła mieszkają w Pęku kluczy i Secure Enclave, nie w tym pliku.

Po starcie wszystko jest zablokowane

Nie istnieje trwały zapis „to jest odblokowane”. Przy wczytywaniu każda apka wraca jako .locked - to celowe wzmocnienie bezpieczeństwa (restart = ponowne pytanie o Touch ID).

Sources/PrivioCore/Enforcement/EnforcementState.swift - budowa stanu startowego

public static func loaded(from store: ConfigStore) -> EnforcementState {
    let persisted = store.load()
    let apps = persisted.apps.map {
        // brak trwałego „unlocked" - startujemy zablokowani
        ProtectedAppSnapshot(app: $0, status: $0.protectionEnabled ? .locked : .unprotected)
    }
    return EnforcementState(apps: apps, configuration: persisted.configuration)
}

Zgodność wstecz: starszy plik nadal działa

Gdy w nowej wersji dochodzi nowe pole, stare pliki go nie mają. Dlatego dekodowanie jest defensywne: brakujące pole dostaje bezpieczną wartość domyślną zamiast wysypać wczytywanie. To dlatego w kodzie widać wzorzec „weź jeśli jest, w przeciwnym razie użyj domyślnej”.

wzorzec z EnforcementState.init(from:)

// „zdekoduj jeśli obecne, inaczej wartość domyślna" - stare pliki działają dalej
apps = try c.decodeIfPresent([ProtectedAppSnapshot].self, forKey: .apps) ?? []
protectionActive = try c.decodeIfPresent(Bool.self, forKey: .protectionActive) ?? true

Integralność: czy plik nie został podmieniony

Stan niesie flagę configIntegrityValid - Privio potrafi sprawdzić (skrótem kryptograficznym, HMAC), czy zapisany plik konfiguracji nie został zmieniony poza aplikacją. Jeśli coś się nie zgadza, apka może zareagować, zamiast ślepo ufać zmienionym danym.

14 - Egzekwowanie od środka

Egzekwowanie od środka: co naprawdę robi „strażnik”

Najgłębsze spojrzenie na mózg. Aktor to nie tylko „stan” - to zestaw drobnych mechanizmów, które razem sprawiają, że blokowanie jest szybkie, spójne i nie wpada w pętle.

Sources/PrivioCore/Enforcement/InProcessEnforcementService.swift - pola aktora

public actor InProcessEnforcementService: EnforcementControlling {
    private var state: EnforcementState                   // cały ulotny stan
    private var foregroundBundleID: String?               // co jest teraz na wierzchu
    private var recentlyCancelled: [String: Date] = [:]   // anty-pętla po anulowaniu
    private let cancelCooldown: TimeInterval = 0.8
    private let maxActivityEntries = 200                  // limit historii zdarzeń
    private var monitorTask: Task<Void, Never>?           // nasłuch zdarzeń w tle
    private var systemTask: Task<Void, Never>?            // nasłuch zdarzeń systemu
}
Śledzenie pierwszego planu i chowanie

Aktor pamięta, która apka jest na wierzchu (foregroundBundleID). Gdy na wierzch wychodzi apka chroniona i zablokowana, natychmiast prosi o schowanie okna i uruchamia uwierzytelnianie. To ta ścieżka, która daje charakterystyczne mignięcie.

Anty-pętla po anulowaniu (cooldown)

Gdyby po anulowaniu Touch ID apka od razu znów wracała na wierzch, powstałaby irytująca pętla „anuluj → apka wraca → znów prośba”. Dlatego po anulowaniu jest krótkie okno wyciszenia (cancelCooldown = 0,8 s), w którym Privio nie ponawia natychmiast prośby dla tej samej apki.

Jedna ścieżka dla auto-blokady i przycisku „Unlock”

To, dla której apki trwa właśnie pytanie o tożsamość, trzyma jedno pole w stanie (pendingAuthAppID). Dzięki temu automatyczne pytanie (po wejściu na zablokowaną apkę) i ręczne kliknięcie „Unlock” korzystają z tej samej logiki, a interfejs po prostu pokazuje/chowa okno auth, patrząc na to jedno pole.

Minutniki bezczynności: jeden lekki „sen” na klucz

Zamiast ciągłego sprawdzania zegara, każdy odliczany czas to osobne, anulowalne „uśpienie zadania”. Prawdziwa wersja używa systemowego zegara; wersja testowa - ręcznego, który można „przewinąć” w teście deterministycznie. To ta sama abstrakcja (InactivityScheduling), tylko dwie różne implementacje.

Zdarzenia systemu: blokada ekranu i uśpienie

Osobny nasłuch (systemTask) reaguje na zablokowanie ekranu i uśpienie Maca - wtedy chronione apki są blokowane zgodnie z ich ustawieniami. To wszystko dzieje się w izolacji aktora, więc nie koliduje z resztą zdarzeń.

Publikacja zmian: migawki, nie „szturchanie” UI

Po każdej zmianie aktor buduje nową migawkę EnforcementState i wysyła ją strumieniem do subskrybentów (historia jest przycinana do maxActivityEntries). UI tylko odbiera i renderuje - aktor nigdy nie sięga do UI bezpośrednio.

15 - Poza szwem

Moduły poza szwem: dlaczego nie wszystko przechodzi przez mózg

Część funkcji świadomie NIE korzysta z „szwu” enforcementu. Powód jest prosty: one sterują bezpośrednio oknami systemu albo samym macOS, więc żyją w warstwie aplikacji, obok mózgu - nie w nim.

Zasłona prywatności - nakładki na okna

To zestaw przezroczystych, nieklikalnych paneli układanych na samej górze ekranu. Cele (które okna zasłonić) Privio wylicza, pytając system o położenie i rozmiar okien - nie o ich obraz - więc nie potrzebuje prawa do nagrywania ekranu. Tryb „reflektor” wycina w rozmyciu ruchomą dziurę wokół kursora specjalną maską. Cała rzecz żyje w warstwie aplikacji, bo steruje oknami AppKit.

Prywatny sejf - kontroler + narzędzie systemowe

Sejf ma własny kontroler działający na głównym wątku i reaktywny (@Observable), a ciężką robotę (tworzenie/montowanie zaszyfrowanego obrazu) wykonuje osobny aktor wołający narzędzie hdiutil. Kluczowa zasada bezpieczeństwa lifecycle: sejf nigdy nie może zgłosić fałszywego „zamknięty” - jeśli odmontowanie się nie uda (otwarte pliki), status zostaje „otwarty” z uczciwym błędem, a zamknięcie apki jest wtedy wstrzymywane.

Blokada po odejściu - czysta logika + sprzęt osobno

Decyzję „czy zablokować” podejmuje czysta, w pełni testowalna maszyna stanów w mózgu (Core) - ze wszystkimi zabezpieczeniami przed przypadkowym zablokowaniem. Sprzęt (odczyt Bluetooth, skaner sygnału zegarka, komenda blokady ekranu) żyje osobno w warstwie aplikacji. Ten podział to powód, dla którego najbardziej wrażliwą logikę można sprawdzić testami, bez żadnego prawdziwego urządzenia.

◆ ZasadaReguła jest spójna: logika, którą da się przetestować, idzie do mózgu (Core); to, co dotyka sprzętu i okien, zostaje w warstwie aplikacji. Dzięki temu granica „szwu” pozostaje wąska i czysta.
16 - Testowalność

Testowalność: skąd wiadomo, że to naprawdę działa

Cała architektura z rozdziałów 8-9 ma jeden praktyczny cel: dać się sprawdzić automatycznie, bez klikania i bez prawdziwego sprzętu. Oto jak.

🎭

Atrapy zamiast sprzętu

fakes

Bo mózg przyjmuje współpracowników przez protokoły, w testach podaje się atrapy: „ręczny” minutnik, który można przewinąć na żądanie, albo „ręczny” monitor Bluetooth, który udaje oddalenie urządzenia. Testy są przez to szybkie i powtarzalne.

📸

Tryb podglądu (snapshot)

PRIVIO_SNAPSHOT

Uruchomienie apki ze specjalną zmienną renderuje interfejs „na sucho”, poza ekranem, i kończy pracę - bez podłączania monitorów i bez chowania jakichkolwiek apek. Idealne do sprawdzania wyglądu bez ryzyka.

🧪

Czysta logika osobno

testowane jednostkowo

Najbardziej wrażliwe decyzje są wydzielone jako czyste funkcje: generowanie/weryfikacja klucza odzyskiwania sejfu, reguły bezpiecznego importu plików, maszyna stanów blokady po odejściu. Każda ma własny zestaw testów.

🚧

Granica chroni testy

seam + protokoły

Ponieważ UI zna tylko kontrakt, a mózg tylko protokoły współpracowników, każdą warstwę da się testować w izolacji. To nie „dodatek” - to główny powód, dla którego architektura wygląda właśnie tak.

17 - Dystrybucja

Dystrybucja i aktualizacje: jak apka trafia do użytkownika

Gotowy kod to jeszcze nie gotowy produkt. Oto jak Privio jest składane, podpisywane i aktualizowane.

Projekt jako plik tekstowy (XcodeGen)

Ustawienia projektu żyją w czytelnym pliku project.yml, a właściwy projekt Xcode jest z niego generowany komendą. Zysk: konfiguracja jest w git, nowe pliki źródłowe dołączają się automatycznie, i nikt niczego nie „odklika” przypadkiem.

Podpis i zaufanie systemu

Privio jest podpisane certyfikatem Developer ID i notaryzowane przez Apple - system potwierdza wtedy autora i to, że pliku nikt nie podmienił. Dzięki temu macOS uruchamia aplikację bez ostrzeżeń, mimo że pochodzi spoza App Store.

Aktualizacje (Sparkle)

Nowe wersje dostarcza sprawdzony mechanizm Sparkle: apka pobiera „kanał” z listą wydań, a każda paczka jest podpisana, więc nie da się podsunąć podrobionej aktualizacji. Dzięki temu można wydawać poprawki bez App Store.

Dlaczego poza App Store

Żeby zarządzać innymi aplikacjami, Privio nie może działać w piaskownicy - a bez piaskownicy nie ma wejścia do App Store. Stąd dystrybucja bezpośrednia, za to z pełnym podpisem i notaryzacją zamiast „pieczątki” sklepu.

18 - Spójny wygląd

Spójny wygląd: jak interfejs trzyma się kupy

Na koniec warstwa, którą widać najbardziej. Żeby apka wyglądała jak jedna całość, a nie zlepek ekranów, jej wygląd jest zbudowany na kilku wspólnych fundamentach - dokładnie tak samo, jak w web-devie robi się to zmiennymi CSS i komponentami.

🔤

Jeden krój, jeden zestaw ikon

Nunito + Font Awesome

Cały tekst używa Nunito (ten sam font co strona marki), a wszystkie ikony pochodzą z Font Awesome. Jest nawet warstwa tłumacząca stare nazwy ikon na nowe, żeby nie trzeba było przepisywać całego kodu przy zmianie zestawu.

🎨

Kolory z jednego źródła

nie hardkodowane

Kolory są nazwane i trzymane w jednym katalogu (osobno dla trybu jasnego i ciemnego), a kod odwołuje się do nazw, nie do konkretnych wartości. Zmieniasz kolor w jednym miejscu - zmienia się wszędzie. To dokładnie idea zmiennych CSS.

🪟

Jeden szkielet okien

wspólny komponent

Wszystkie okna dialogowe używają jednego wspólnego „szkieletu” (nagłówek, marginesy, przyciski). Dzięki temu każde okno wygląda tak samo z definicji - nie da się przypadkiem zrobić okna „nie po bożemu”.

✒️

Logo rysowane kodem

wektor, nie obrazek

Znak Privio (litera „P” z linii papilarnych) jest rysowany w kodzie jako wektor, więc da się go przekolorować i wyświetlić w dowolnym rozmiarze bez utraty ostrości - również do wygenerowania ikony aplikacji.

◆ Koniec podróżyOd „portiera przy drzwiach” z rozdziału 01 doszedłeś do montażu zależności, wnętrza aktora, trwałości danych, modułów systemowych i warstwy wyglądu. To jest pełny przekrój tego, jak Privio jest zbudowane - te same zasady, konsekwentnie zastosowane na każdym poziomie.