Files
homelab-upgrade/docs/IMPLEMENTATION.md
homelab-updaterandClaude Opus 5.5 aec96b2273
ci / check (push) Successful in 1m29s
ci / publish (push) Successful in 1m44s
chore(web): style the existing notes, drop a heading rule cards outgrew; docs for phase G
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:55:12 +02:00

20 KiB
Raw Permalink Blame History

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-<app>, 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 <secret:>/<input:>, .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).