# Plan rozbudowy silnika workflow Stan na 2026-09-20. Ten dokument jest operacyjnym planem przyjętym przez właściciela. Decyzje architektoniczne pozostają w `docs/DESIGN.md`; tutaj zapisujemy etapy, kryteria odbioru i stan realizacji. ## Tryb pracy Od 2026-09-21 realizacja przebiega ciągle, pod odpowiedzialnością jednej sesji prowadzącej. Rezygnujemy z osobnej pętli Sol → Terra → Sol dla każdego etapu, ponieważ wielokrotne ponowne czytanie tego samego zakresu nadmiernie wydłużało pracę i zużywało kontekst. - Sesja prowadząca realizuje kolejne etapy bez obowiązkowego przekazywania ich między modelami. - Testy uruchamiamy po spójnych partiach funkcjonalnych, a pełny `npm run check` przed każdym commitem. - Jeden przekrojowy audyt architektury, bezpieczeństwa, migracji i wdrożenia odbywa się przed końcowym przełączeniem na VM103. - Dodatkową sesję wykorzystujemy tylko dla konkretnego problemu, którego równoległe rozwiązanie daje wyraźną korzyść; nie jest to obowiązkowa bramka etapu. Każda sesja czyta `AGENTS.md` i `TASK_STATE.md` na start. `docs/DESIGN.md` i ten dokument czyta się przy pracy nad architekturą albo nowym etapem, nie w całości przy każdej drobnej zmianie — patrz `TASK_STATE.md` po bieżący stan. Nie dotyka VM103, Proxmoxa ani działających kontenerów przed końcowym, kontrolowanym wdrożeniem. Istniejące lokalne zmiany właściciela muszą pozostać nienaruszone. ## Założenia przekrojowe - Nowy subsystem powstaje obok obecnego drivera aż do kontrolowanego przełączenia. - XState 5 jest czystym silnikiem przejść; I/O pozostaje w adapterach. Persistujemy własne projekcje i eventy domenowe. - Opublikowane workflow, funkcje i profile są niemutowalne. Instancja ma pełny snapshot zależności. - W kolejce może być wiele instancji `queued` i `waiting`; globalnie istnieje najwyżej jedna `running`. - Użytkownik może składać kroki w generyczne workflow. Funkcje użytkownika i uprzywilejowane akcje systemowe są odrębnymi mechanizmami bezpieczeństwa. - Adapter dostawcy modelu nie przenika do domeny. Profil wybiera wykonawcę jawnie i nie ma automatycznego fallbacku. - Event ledger jest źródłem osi czasu aplikacji oraz odtwarzalnego bieżącego stanu. ## Decyzja wdrożeniowa 2026-09-21 - Dzisiejszy przebieg nocny jest anulowany. Na VM103 `NIGHTLY_AT` został wyzerowany, odtworzono wyłącznie usługę `driver` i potwierdzono brak przebiegu `nightly-20260921*`. - Prywatna kopia `.env` sprzed zmiany znajduje się obok manifestu jako `.env.before-disable-nightly-20260921`; nie trafia do repozytorium. - Po zakończeniu wszystkich etapów nowy system ma zostać wdrożony na VM103, ale harmonogram nocny pozostaje wyłączony. Właściciel będzie najpierw wykonywał testy ręczne. - `NIGHTLY_AT` wolno ponownie ustawić dopiero na osobne, jawne polecenie właściciela po odbiorze testów ręcznych. - Końcowe wdrożenie obejmuje backup, `docker compose config --quiet`, aktualizację tylko wymaganych usług oraz weryfikację `ps`, health, logów i endpointu. Nie używamy `down -v` ani nie usuwamy danych. ## Etapy ### Etap 1 — katalog workflow i instancje wykonania Status: **zakończony i zweryfikowany 2026-09-21**. Fundament wersjonowania i deterministycznego lifecycle'u, nieużywany jeszcze przez produkcyjny driver: niemutowalny katalog workflow (`draft → validate → publish`, monotoniczne wersje), czysta maszyna stanów `queued/running/waiting/succeeded/failed/cancelled`, `WorkflowInstanceStore` z atomowym `claimNext(now)` i `RESUME`, migracja SQLite v10 (nowe tabele, bez zmian istniejących). Pełna specyfikacja kontraktów jest w kodzie (`domain/ports.ts`, `infra/sqlite/workflow-catalog*`) i testach — nie duplikujemy jej tutaj. Zweryfikowane gwarancje: najwyżej jedna instancja `running` globalnie, dowolnie wiele `queued`/`waiting`, `RESUME` zawsze do `queued` (nigdy do `running`), event+projekcja commitują się razem, publikacja odrzuca zmienioną/nieważną rewizję. Poza zakresem etapu 1: integracja z driverem/HTTP/UI, graf node'ów, funkcje/akcje/profile modeli. ### Etap 2 — graf i interpreter Status: **zakończony 2026-09-21**. Graf v2 (`schemaVersion: 2`, unia obok nieprzezroczystego v1): `entryNodeId`, jawne przejścia po eventach, typy core `emit`/`wait`/`subworkflow`/`forEach`/`succeed`/`fail`, rozszerzenia przez discriminated union. DAG bez rekursji, jedyna iteracja to sekwencyjny `forEach` (items zamrożone przy wejściu). Walidator publikacji odrzuca duplikaty, dangling targets, nieosiągalne node'y, cykle (w tym pośrednie, iteracyjnie) i przekroczone limity. Interpreter jest pure reducerem bez I/O, z trwałym stosem ramek (`workflow`/`forEach`/`subworkflow`, bez rekursji JS). Migracja SQLite v11 dodaje `workflow_instance_execution` (frame stack, immutable input), append-only `workflow_execution_events` i `node_attempts` (attempt token, parent, status). `claimNext` pod `BEGIN IMMEDIATE` zapisuje CLAIM + projekcję + execution event + nowy attempt w jednej transakcji; finalizacja poza transakcją wymaga zgodnego `expectedSeq`/tokenu. Po restarcie tylko wygasła próba `running` → `interrupted`; node nie jest nigdy ponawiany po cichu. Replay: fold ledgeru od immutable inputu odtwarza frame stack/node/sequence; rozbieżność z projekcją to błąd integralności, nie cicha korekta. Porty read-only (`getExecutionPosition`, `listNodeAttempts`, `getReplayStatus`) przygotowują dane pod etap 7, bez endpointów/UI na tym etapie. Zweryfikowane kryteria odbioru: wielopoziomowy `subworkflow`/`forEach` przeżywa restart z identycznym replay; cykle/dangling/limity odrzucone przed publikacją; crash w dowolnym punkcie transakcji nie rozdziela stanu; stale token nie finalizuje nowszej próby; driver pozostaje niepodłączony. ### Etap 3 — funkcje i reguły użytkownika Status: **zakończony 2026-09-21**. Wersjonowane funkcje TypeScript/POSIX shell w SQLite, kontrakt `{event, summary, data}`, obowiązkowe przypadki testowe, capability metadata, port testowego wykonania i deterministyczne fejki. Migracja v12 utrwala drafty, przypadki, niemutowalne wersje i append-only test runs. Node rozszerzenia `function` wskazuje dokładne `functionId@version`; snapshot i tworzenie instancji odrzucają brakującą albo zmienioną wersję. Produkcyjny driver nadal nie uruchamia źródła funkcji — izolowany runtime pozostaje etapem 6. ### Etap 4 — akcje uprzywilejowane i odwzorowanie aktualizacji Zamknięty rejestr dozwolonych akcji executora oraz polityki ich użycia. Obecny łańcuch aktualizacji zostaje zapisany jako workflow z zachowaniem wszystkich bramek, kopii, verify i decyzji STOP. Docker image prune może pojawić się dopiero jako jawna akcja z osobną regułą i testami bezpieczeństwa. ### Etap 5 — profile modeli i sidecary Jawne, wersjonowane profile modeli; adaptery Claude i Codex za `ModelExecutor`, osobne sidecary i brak cichego fallbacku. Profile Codex wystawione przez MCP. Architektura adapterów gotowa na kolejnego dostawcę, np. Gemini. ### Etap 6 — izolowany runtime funkcji Efemeryczne wykonanie funkcji użytkownika z limitami czasu, pamięci i wyjścia, domyślnym brakiem sieci, jawnymi capabilities, maskowaniem oraz pełnymi eventami audytowymi. ### Etap 7 — API i edytor webowy Endpointy katalogu i instancji, Monaco dla funkcji oraz Cytoscape dla grafu. Generyczna zmiana kolejności, przypinanie pozycji, rozwijanie zagnieżdżeń, osobne metadane layoutu i diagnostyka walidacji. Oś czasu aplikacji w stylu Jira pokazuje eventy, przejścia stanów i aktualny node. ### Etap 8 — shadow mode i kontrolowane przełączenie Uruchomienie nowego workflow w trybie dry-run/shadow, porównanie decyzji z obecną kolejką, usunięcie rozbieżności i dopiero potem przejęcie claimowania. Po odbiorze następuje kontrolowane wdrożenie nowego systemu na VM103 z pustym `NIGHTLY_AT`: usługi mają działać, ale automatyczny przebieg nie może się utworzyć. Właściciel testuje ręcznie. Włączenie nocy, prawdziwe aktualizacje oraz wycofanie starej ścieżki wymagają osobnego odbioru właściciela. ## Dziennik etapów | Etap | Plan Sol | Implementacja Terra | Weryfikacja Sol | Stan | |---|---|---|---|---| | 1 | 2026-09-20 | 2026-09-20–21 | PASS 2026-09-21 | zakończony | | 2 | 2026-09-20 | 2026-09-21 | 1214 testów PASS | zakończony | | 3 | 2026-09-21 | 2026-09-21 | 1225 testów PASS | zakończony | | 4–8 | ramowy | realizacja ciągła | audyt przekrojowy przed wdrożeniem | w toku | ## Decyzje i zakres — 2026-09-22 Etapy 4-7 powstawały równolegle w kilku sesjach (Codex, Gemini) bez koordynacji; `npm run check` był czerwony (36 błędów `tsc`, 132 `eslint`), mimo że `TASK_STATE.md` deklarował zieloną walidację. Faza A (ta sesja) przywróciła zielony `npm run check` bez zmiany zachowania starej ścieżki drivera i bez nowych funkcji. Przy tej okazji właściciel potwierdził wizję i podjął następujące decyzje: - **Wizja**: silnik przepływów per aplikacja (przepływ generowany przez Claude), z podglądem na żywo bieżącego kroku, sterowaniem całym runem, dodawaniem kroków/bramek/rozgałęzień, blokami shell/TS i agentem wpinanym w dowolne miejsce procesu; proste edytory skryptów. - **Izolacja runtime'u funkcji pozostaje niepełna świadomie**: bez fail-closed na `bwrap`, bez egzekwowania `filesystemRead/Write`, bez twardego limitu pamięci dla `posix`. Bezpieczeństwo i ograniczenia przyjdą później — na teraz ma być prosto i skutecznie. - **Modele**: Claude i Codex są pierwszoplanowe; Gemini to tylko gotowość architektury adaptera (`GeminiSidecarAdapter`), bez sidecara ani wdrożenia. - **Modyfikacja przebiegu = nowa wersja na kolejny run.** Trwający run kończy się na swojej wersji (zgodne z niemutowalnością wersji i replayem — patrz Etap 1-2). - **BPMN**: ocena gotowej biblioteki BPMN zamiast własnego silnika grafu odbędzie się dopiero po zakończeniu Etapu 8. Do tego czasu zostaje obecny graf (`WorkflowDocumentV2` + Cytoscape). ### Luki między wizją a stanem etapów 4-7 (Faza C) — stan 2026-09-23 1. **Przepływ per aplikacja**: zrobione jako kopia szablonu (`update-`, seed); nieedytowane przepływy idą za zmianami szablonu (historia skrótów szablonu). Generowanie przez Claude — nie ma. 2. **Start, cancel, resume z API/UI**: zrobione (shadow/real, cancel, retry po restarcie). 3. **Podgląd na żywo**: zrobione (graf instancji wycentrowany na bieżącym kroku, SSE). 4. **Ogólny węzeł „agent”**: nie ma. ### Faza B — domknięcie poprawności łańcucha aktualizacji (zakończona 2026-09-22) Zamknęła 5 braków z Fazy A: STOP `forbidden_change` na `unexpected_change` w `apply_dry_run`/`apply`; prawdziwy retry/healthcheck w `verify` (reużywa `domain/verify.ts`); Codex MCP (`src/main/codex-mcp-stdio.ts`); fail-closed `maxCostUsd` w `model-dispatcher.ts`; test HTTP dla `.../decisions`. Deny-lista kroku `migrate`/`repair` nie wymagała zmiany kodu — dziedziczy ochronę z `app/executor/agent.ts` (nietkniętego przez etapy 4-7). Pełny opis w `TASK_STATE.md`; commity `b02f1aa..88790bc`. ### Faza D — plan (zatwierdzony przez właściciela 2026-09-23) Decyzje: model sam wykrywa migracje w release notes i przygotowuje plan (compose, kroki, potrzebne dane); właściciel zatwierdza na bramce i podaje dane w dynamicznym formularzu (sekrety poza ledgerem); zatwierdzony diff compose wchodzi bez deny-listy (nigdy `homelab-upgrade`). Każdy workflow może mieć cron. Brak listy aplikacji pod absolutnym STOP (decydują `enabled`/`apply`). - **A** rollback na bramce po kontroli modelu; czytelne bloczki w grafie (ludzka nazwa, rodzaj, opis, `resolve_*` przy bramce). - **B** migracje: operacja `migration_plan` (opus, sesja `observe`, schemat `MigrationPlan`), bramka `gate_migration` z formularzem `inputs` (wartości w tabeli `workflow_run_inputs`, kasowane po użyciu), akcja `docker.migrate@1` w executorze (placeholdery ``/``, `.env` 0600, `compose config -q`), `post_upgrade` wykonuje `stepsAfter`. - **C** czyszczenie Dockera: akcje `docker.diskUsage@1`, `docker.imagePrune@2` (unused/until, build cache, nigdy wolumeny), `decisionContext` dla `destructive` z runnera, przepływy systemowe (bez aplikacji) startowane z UI, przepływ `docker-cleanup` zakładany przez UI (funkcja `assess_cleanup`, próg 10 GB). - **D** cron per workflow (tabela `workflow_schedules`, parser w `domain/cron.ts`, driver co minutę, te same blokady co ręczny start); rozłożenie aktualizacji w tygodniu; `docker-cleanup` `0 6,18 * * *` czyści sam przy < 10 GB. - **E** edytor: formularze per rodzaj węzła zamiast surowego JSON, opis tego, co blok wykona, skąd bierze dane i dokąd prowadzą zdarzenia; dodawanie/usuwanie węzłów. Kolejność: A → B → C → D → wdrożenie → `docker-cleanup` przez UI z cronem → E → test migracji na żywo (influx 1.8 → 1.11; migracja 2.x tylko na wyraźne polecenie właściciela, z kopią `/srv/appdata/grafana`). Status (2026-09-23): A, B, C, D zrobione (`ffed859..a202c1d`). Różnice względem planu: - `docker.migrate` robi całą aktualizację (compose + `.env` + przypięty obraz, `pull`, `up` całego projektu) i prowadzi prosto do `verify_initial`; po `backup` rozgałęzia funkcja `app-update.route-update`. `rollback` przywraca też `.env`. - Polityka aktualizacji ma rewizję 2 (`docker.migrate`, `docker.diskUsage`, `docker.imagePrune@2` z progiem 10 GB); seed podnosi starszą rewizję. `imagePrune@2`: dozwolony poniżej progu albo po `approve`/`proceed` właściciela tuż przed węzłem. - Harmonogramy sprawdza serwer (co 20 s, raz na minutę dzięki `workflow_schedules.last_minute`), strefa `SCHEDULE_TZ`. - `docker-cleanup` bez bramki: przy ≥ 10 GB wolnego kończy się `terminal_not_needed`. - Harmonogramów aktualizacji aplikacji nie ustawiono — decyzja właściciela (kolumna Schedule na liście workflow). ### Faza E — uproszczenie dla człowieka (zatwierdzona przez właściciela 2026-09-23) Decyzje: stary łańcuch kroków usunięty całkiem (z UI i backendu), Codex podłączony przez dispatcher profili, UI i porządki w kodzie w jednym planie. - **1** Overview jako strona startowa (czeka na Ciebie / trwa / skończone / następne), badge = runy `waiting`; „Instances” → „Runs”. - **2** Czytelne runy i bramki: lista z aplikacją, krokiem/wynikiem i filtrami; na stronie runu „Why it failed”; bramka z przyciskiem na akcję i potwierdzeniem rollback/skip. - **3** Jedno nazewnictwo („Dry run”/„Real”), harmonogram z presetami na stronie aplikacji, potwierdzenia. - **4** Edytory: „Publish” zapisuje, sprawdza (i testuje funkcję) i publikuje; błędy nie zasłaniają edytora. - **5** Wycofanie łańcucha: pushe z pętli workflow, statusy Apps z runów, `data_version` bez step-store; usunięte Queue/Runs/Agent, driver kroków, demo, dry-run, watchdog, `NIGHTLY_AT`/`DEMO`/`DRIVER_IDLE_MS`/`WATCHDOG_GRACE_MIN`. Tabele `runs`/`steps`/`reminders` zostają nieużywane. - **6** Codex przez `createModelDispatcher`. - **7** Porządki bez zmiany funkcji (wymagane porty, podział `http/server.ts`, jeden rejestr rozszerzeń, bez fallbacku runtime). Status (2026-09-23): 1–7 zrobione na `foundations`; w 7 pominięte podział `http/server.ts` i przeniesienie walidacji grafu do `app/` (przeniesienie, nie uproszczenie). Bez squasha migracji. - v1 usunięte za zgodą właściciela po odczycie bazy VM103 (brak użyć): dokument/snapshot workflow v1, węzły `function`/`privilegedAction` v1, akcje `docker.inspect/preflight/apply/imagePrune@1`, reguła prune v1 (`recentBackup`). Zostaje projekcja wykonania v1 (jeden zakończony run na VM103 jej używa) i jedyne wersje `componentOperation`/`ownerDecision` v1. - Codex: oba sidecary mówią tym samym kontraktem `/run` (`http/model-server.ts`), więc driver ma po jednym `createHttpModelClient` na dostawcę pod `createModelDispatcher`; osobne adaptery Claude/Codex/Gemini usunięte (wysyłały pola, które ścisły kontrakt odrzuca). Żądanie bez przypiętego profilu idzie do profilu o nazwie wykonawcy (`sonnet`/`opus`), więc przełączenie profilu na Codex na stronie Models kieruje tam modele aplikacji. Profil narzuca limit czasu i kosztu; domyślne profile mają 30 min (seed podnosi nietknięty pierwszy dokument). ### Faza F — zaległości bezpieczeństwa, frontend, Arcane (zatwierdzona przez właściciela 2026-09-24) Decyzje: zostajemy przy własnym silniku (BPMN nie daje gwarancji projektu: jeden węzeł globalnie, snapshot zależności, własna oś czasu); Dockge zastępuje Arcane z compose w git (`docs/DESIGN.md`, „Arcane i compose w git”). Proste części etapów robi Sonnet w osobnych worktree, trudne i przeglądy sesja prowadząca. - **0** przegląd UI na żywo — pominięty (brak rozszerzenia Chrome w sesji). - **1** wartości formularza migracji kasowane po zakończeniu runu (driver przy starcie i po każdym węźle, serwer przy anulowaniu). - **2** maskowanie sekretów z env i sekretnych pól formularza w wynikach węzłów; wyniki tylko dokładnymi wartościami (heurystyka psułaby `${NAME}` w compose planu migracji), błędy także heurystyką. - **3** CLI `codex` w obrazie (`@openai/codex` przypięty, `CODEX_HOME=/codex`); logowanie jednorazowe w `docs/DEPLOY.md`. - **4** push HA na bramce — kod był; potwierdza właściciel. - **5** plan migracji zapamiętuje `composeBefore`; bramka pokazuje diff compose i ma kolejność: co się zmienia → co mówi model → czego potrzeba → decyzja. - **6** log sesji modelu na żywo: driver co 3 s czyta `/session/log` (tabela `workflow_model_activity`, schemat v24), timeline zwraca `modelSessions`, strona runu pokazuje go na żywo i po fakcie. - **7** Apps: Running/Available/Next run; podgląd wzorca tagów (`POST /api/apps/:app/tag-preview`). - **8** widoki na telefonie: nawigacja się zawija, tabele chowają kolumny drugorzędne poniżej 720 px, przyciski decyzji na pełną szerokość, niższy graf. - **9** Arcane i repo compose: 9a–9e wykonane. Prywatne repo `homelab-compose` na `master` zawiera 19 projektów i archiwum Dockge; Arcane v2.13.1 ma włączony autosync co 5 minut dla 18 projektów, a executor obsługuje publikację i rollback git oraz `hl write/up`. Dockge zatrzymano, a jego compose/appdata i 29 starych kopii przeniesiono do `/srv/appdata/to_delete/20260930`; 20 wpisów harmonogramu zapisanych i sprawdzonych przez API. - Podglądy harmonogramów: `nextCronRuns` oblicza kandydatów według godziny UTC z jednym formatterem Intl zamiast tworzyć formatter dla każdego kandydata; zachowane są obsługa DST, rozdzielczość 30 minut i strefy takie jak Asia/Kathmandu. Hotfix wdrożono na lokalnym obrazie; przejście na obraz CI wymaga `update.sh`. `npm run check`: 104 pliki, 1240 testów PASS. ### Faza G — nowoczesny, graficzny frontend (zatwierdzona przez właściciela 2026-09-30) Decyzje: sidebar + karty, ciemny motyw domyślny z przełącznikiem; wykresy w ECharts (rejestrowane tylko potrzebne części, osobny chunk ładowany leniwie); własne okno potwierdzeń, panel boczny i toasty zamiast `window.confirm`; bez zmian w backendzie — agregacje w przeglądarce z istniejących endpointów (`src/web/stats.ts`), budowniczki opcji wykresów jako czyste funkcje (`src/web/charts/options.ts`), kolory z tokenów CSS. Gałąź `frontend-refresh`. - **G0** powłoka: sidebar z ikonami (na telefonie górny pasek), tokeny powierzchni/cieni, `Card`, `StatTile`, `Icon`, przełącznik motywu. - **G1** okna: `DialogProvider` (`useConfirm`, `useToast`), `Drawer`; podgląd runu w panelu z listy Runs. - **G2** ECharts i agregacje: runy dziennie wg statusu, wskaźnik sukcesu z okresem poprzednim, czasy trwania, koszt dziennie wg modelu; paleta kategorii sprawdzona pod daltonizm. - **G3** Overview: kafle KPI, aktywność 30 dni, harmonogramy na 7 dni. - **G4** Apps: karty projektów (wersje, 10 ostatnich runów, następny run), przełącznik karty/tabela; strona aplikacji z wykresem czasu trwania runów. - **G5** strona runu: Gantt kroków (klik zaznacza węzeł), historia w oknie. - **G6** Models: koszt dziennie wg modelu, udział modeli, wyniki wg rodzaju pytania; edycja profilu w panelu bocznym. - **G7** porządki stylów, dokumentacja. Przegląd na żywo w przeglądarce jeszcze nie zrobiony (brak przeglądarki na hoście). ### Faza H — wygoda na co dzień (zatwierdzona przez właściciela 2026-09-30) Decyzje: logowanie Keycloak dla wszystkich (oauth2-proxy jako forwardAuth Traefika) i dostęp spoza LAN; cisza nocna 01:00–07:30 (wieczorne awarie pushują od razu), w ciszy nic nie dzwoni, bramki i błędy trafiają do porannego raportu o 7:30; godziny zmienia się w UI. Etapy wdrażane przyciskiem `deploy` bez osobnej zgody. - **H1** stan „tu i teraz”: driver co minutę zbiera `docker ps` kontenerów compose (przez proxy tylko do odczytu) i rekord vzdump, zapisuje migawkę w `settings`; Overview pokazuje zdrowie aplikacji i wiek kopii VM. - **H2** historia wersji aplikacji (z runów: z czego na co, którym runem, naprawy i rollbacki) i oznaczenie aplikacji, które często wymagają uwagi. - **H3** ustawienia powiadomień w UI, cisza nocna i poranny raport (push + karta „Ostatnia noc” na Overview). - **H4** decyzja z telefonu: strona runu z bramką na górze i przyciskami przyklejonymi do dołu ekranu; push otwiera ją bezpośrednio. - **H5** czasy względne, szybkie wyszukiwanie `Ctrl+K`, dry run wszystkich aplikacji z dostępną aktualizacją. - **H6** logowanie Keycloak i zdjęcie ograniczenia do LAN. Status 2026-09-30: H1–H6 wdrożone na VM103 (ostatnio `6cf5848`). Przy pierwszym wdrożeniu H6 compose odrzucił inline config w usłudze `read_only`, a `update.sh` przerwał się bez wycofania (ok. 12 min awarii, wycofane przyciskiem do `fc68d30`); poprawione: `auth` bez `read_only`, nieudane `up -d` wraca do poprzedniego obrazu i compose. Tego samego dnia dysk VM103 zapełnił się obrazami aplikacji i cache buildów CI: `update.sh` zostawia teraz uruchomiony, poprzedni i 3 najnowsze obrazy, CI czyści cache starszy niż 2 dni.