Tresc byla w osobnym pliku 2026-08-07.md (8031396) mimo ze praca odbyla sie 2026-08-06 — data przyszla stad, ze 2026-08-06.md byl juz zajety przez sesje fazy mailowej. Sesja 15:25 domyka jednak follow-upy #1 i #5 tamtej sesji, wiec jej miejsce jest w tym samym logu dnia, pod naglowkiem `## Session 15:25`, zgodnie z konwencja z 2026-08-05.md. Nie robie tego amendem:8031396zdazyl trafic na origin (operator zmergowal task/mail-sync-impl fast-forwardem ponad nim), wiec historia jest juz publiczna. Przy przenoszeniu doszla korekta sekcji "Sprzatanie" z sesji 13:20: wpis "actions/dispatch/lustro/ — oproznione przez operatora (zombie re-pull ustal)" nie odpowiadal stanowi faktycznemu. O 13:16 oba pliki (10:39 i 11:08) nadal lezaly w zrodle, a LUSTRO re-pullowalo je co 60 s az do 13:18:23. Katalog zdrenowal sie dopiero w wyniku testu z tej sesji — i mogl, bo dopiero wtedy fix w executorze nadal mu prawa 775. Follow-upy sesji 15:25 numerowane osobno (15:25/#1..#5), zeby nie kolidowac z lista z 13:20. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
25 KiB
| okf | type | visibility | status | updated | links |
|---|---|---|---|---|---|
| 0.1 | session-log | private | active | 2026-08-06 |
Session log 2026-08-06
Session 13:20
Produkcyjna weryfikacja pierwszego cyklu safe-cleanup na LUSTRO (R1/R2/R3, deploy
2026-08-05) oraz pierwszy pełny cykl HITL: approval → dispatch → wykonanie na nodzie →
action_result → completed. Sesja supervised, checkpointy zatwierdzane przez operatora,
approvale wykonywane wyłącznie przez operatora. Incydent źródłowy:
docs/incidents/2026-07-30-ollama-solaria-vanish.md.
Commits
Ta sesja nie wprowadziła żadnych commitów poza niniejszym logiem — zero zmian w kodzie i konfiguracji repo. Cała praca to recon read-only + kontrolowane zapisy runtime na LUSTRO i VPS (kanarki testowe, plik akcji), wszystkie sprzątnięte lub udokumentowane niżej.
Poprzedni log sesji: 67aa092 docs: session 2026-08-05 22:47 — potwierdzony na
origin/master (ahead/behind 0/0) na starcie sesji.
Files changed
Brak — poza tym plikiem.
KROK 1 — safe-cleanup na LUSTRO: obie gałęzie potwierdzone produkcyjnie
Stan wyjściowy
Marker /opt/homelab/state/last-docker-cleanup = 1785925612 (2026-08-05 12:26:52 CEST),
sprzed deployu (obraz node-agenta utworzony 22:42:05 CEST). Bramka
_cleanup_rate_ok() (CLEANUP_INTERVAL_SECS = 86_400) trzymała pierwszy cykl nowego kodu
do 12:26:52 dnia 2026-08-06. Brak linii cleanup w logach do tego momentu był więc
zachowaniem poprawnym, nie awarią.
Kod w runtime zweryfikowany: md5(/app/src/node_agent.py) == md5(repo HEAD) =
c9ac64e10b42b3e0ed9e4c168579bfaa, brak wykonywalnego containers.prune(),
NODE_TYPE=sd_card.
Pułapka interpretacyjna: logi kontenera node-agent są w UTC, host w CEST. Pozorna
6-godzinna dziura w logach to nocny halt (root cron 30 23 * * * /usr/sbin/halt,
boot 06:30) — LUSTRO ma duty cycle jak SOLARIA, zgodnie z inventory/topology.yaml
(duty_cycle: nightly).
Test w oknie prune
Do okna przygotowano trzy kontenery exited pokrywające obie gałęzie filtra:
| Kontener | Polityka / label | Oczekiwane | Wynik |
|---|---|---|---|
prune-disposable |
restart=no, brak compose |
usunięty | ✅ usunięty |
prune-canary |
restart=unless-stopped, brak compose |
zachowany | ✅ przeżył |
node-exporter (realny serwis) |
restart=always, brak compose |
zachowany | ✅ przeżył |
Log z okna (12:27:49 CEST / 10:27:49 UTC):
INFO - Pruned dangling images (0 MB reclaimed)
WARNING - Removed 1 disposable stopped container(s): prune-disposable (kept 2 managed)
Marker zaktualizowany na 1786012069. kept 2 = prune-canary + node-exporter.
Wszystkie kontenery produkcyjne nietknięte.
Werdykt: obie gałęzie filtra działają na produkcji — gałąź ochronna (kontener zatrzymany przez operatora z polityką restartu przeżywa prune; dokładny scenariusz incydentu ollamy) oraz gałąź usuwania (filtr nie jest no-opem, faktycznie kasuje jednorazowe resztki).
Korekta wniosku z 2026-08-05
Wczorajszy test kanarka na SOLARII (przeżył 151 s) nie dowodził działania filtra —
SOLARIA ma NODE_TYPE=lte_node (mitygacja M1), gdzie run_safe_cleanup() kończy się
return przed jakimkolwiek prune. Ten test potwierdził M1, nie R1. Dowód dla R1 powstał
dopiero dziś na LUSTRO.
Stan cleanupu na flocie
| Node | NODE_TYPE | Cleanup |
|---|---|---|
| PIHA | sd_card |
✅ działa (2026-08-05 16:27 CEST: kept 0 managed) |
| LUSTRO | sd_card |
✅ działa (2026-08-06 12:27:49, jw.) |
| SOLARIA | lte_node (M1) |
wyłączony w całości, brak markera |
| VPS | lte_node (M1) |
wyłączony w całości, brak markera |
M1 nadal zdjęte do zrobienia na SOLARII i VPS — do tego czasu te nody nie sprzątają Dockera wcale.
KROK 2 — dlaczego crash-loop watchtowera nie generował akcji
Eventy płynęły poprawnie: evt-lustro-<ts>-containers_not_running-watchtower.json co ~60 s,
11 317 plików w events/lustro/ na VPS (node-agent rsyncuje z --remove-source-files,
stąd pusty katalog lokalny). To nie był problem transportu ani emisji.
Przyczyna: supervisor.reconcile() iteruje wyłącznie po desired_state["services"]
ładowanym z hosts/<node>/services.yaml (supervisor.py:380). hosts/lustro/services.yaml
deklaruje node-agent, node-exporter, piper-tts — watchtowera tam nie ma. Brak wpisu
w desired state ⇒ brak driftu ⇒ brak rekomendacji. Ponad 11 tys. eventów dead-enduje.
Observer natomiast zna lustro/watchtower (incydent inc-1786007596-lustro-watchtower,
status unhealthy) — rozjazd dotyczy wyłącznie supervisora.
Wykluczone: shadow_mode (dotyczy tylko HA_DIAG_SHADOW_MODE, ścieżka HA-diag),
duty_cycle (tłumi wyłącznie liveness node'a), progi/cooldown (containers_not_running
jest w CONTAINER_RESTART_TRIGGERS, dedup po stabilnym ID).
Konsekwencja druga: akcja wstawiona ręcznie do pending/ dla serwisu spoza desired state
żyje jeden cykl supervisora. _cancel_resolved_pending_actions() (supervisor.py:560)
skasował ją po 15 s z powodem service_removed_from_desired_state. Kasowanie dotyczy
wyłącznie pending/ — approved/ i running/ są z założenia nietykalne. Dlatego
akcja ręczna dla watchtowera trafiła ostatecznie prosto do approved/.
KROK 3 — dwa pełne cykle HITL
Wszystkie znaczniki UTC (CEST = +2). Pętla executora: 10 s. Pętla node-agenta: 60 s.
Cykl 1 — node-exporter (ścieżka w pełni organiczna)
| Etap | Timestamp | Δ |
|---|---|---|
docker stop node-exporter (trigger) |
10:22:49 | — |
event → observer → incydent inc-1786011821-lustro-node-exporter |
~10:23:41 | +52 s |
supervisor: Generated recommendation → pending/ |
10:24:00.66 | +71 s |
approval operatora (mv → approved/) |
~10:38:5x | — |
executor: Executing action → running/ |
10:39:00.650 | ≤10 s |
executor: Dispatched … to node-agent on lustro |
10:39:00.657 | +7 ms |
node-agent: rsync-pull + bramki + docker restart |
10:39:23.15 | +22,5 s |
event action_result (success: true) |
10:39:23.293 | +0,14 s |
executor: completed |
10:39:30.752 | +7,5 s |
Approval → completed: 30,1 s.
Cykl 2 — pi-watchtower-1 (akcja utworzona ręcznie, zatwierdzona przez operatora)
| Etap | Timestamp | Δ |
|---|---|---|
operator zapisuje akcję do approved/ |
11:08:38 | — |
executor: Executing action → running/ |
11:08:40.829 | +2,8 s |
executor: Dispatched … (container=pi-watchtower-1) |
11:08:40.838 | +9 ms |
node-agent: Restarted container 'pi-watchtower-1' |
11:08:51.002 | +10,2 s |
event action_result (success: true) |
11:08:51.002 | — |
executor: completed |
11:09:00.909 | +9,9 s |
Approval → completed: 20,1 s. Watchtower wrócił do crash-loopa — zgodnie z założeniem;
sukcesem było przejście pipeline'u i poprawny action_result, nie uzdrowienie kontenera.
Bramki agenta
- Whitelista typu i node scoping — przeszły; logują się tylko przy odrzuceniu, więc dowodem przejścia jest sama egzekucja.
- Self-restart guard — nie dotyczył (cel ≠
node-agent). - Idempotencja — zadziałała na żywo i wielokrotnie:
Action … already processed — skipping (idempotency), markery/opt/homelab/state/processed-actions/<action_id>.done.
Obserwacja uboczna: regeneracja i auto-cancel
O 10:39:48 (18 s po udanym restarcie) supervisor wygenerował ponownie akcję dla
node-exportera, bo world state jeszcze pokazywał unhealthy (opóźnienie observera).
O 10:40:49 sam ją skasował (drift_resolved_auto). Podwójnego restartu nie było, ale
istnieje ~60-sekundowe okno, w którym po udanej remediacji potrafi powstać duplikat.
KROK 4a — rekomendacja ws. poluzowania bramek HITL
Rekomendacja: jeszcze nie, ale wąskie poluzowanie jest obronialne po trzech warunkach.
Za:
- Pipeline przeszedł end-to-end dwukrotnie, w tym raz w pełni organicznie (event → observer → supervisor → approval → executor → node-agent → wynik).
- Czas maszynowy to 20–30 s; wąskim gardłem jest wyłącznie człowiek (dziś ~15 i ~30 min).
container_restartjest tanie i odwracalne, wykonanie jest scoped do node'a, whitelisty jednego typu akcji i guardu self-restartu; egzekutor nigdy nie wchodzi na node po SSH.- Kolejka sama się czyści:
drift_resolved_autokasuje akcje, które przestały być potrzebne, więc opóźniony approval nie powoduje zbędnego restartu. - Idempotencja obroniła się w warunkach bojowych (patrz defekt dispatch niżej).
Przeciw:
- Próbka: 2 wykonania, 1 node, 1 typ akcji, obie ścieżki udane. Ani razu nie zaobserwowano
ścieżki porażki (
success: false), timeoutu akcji wrunning/, ani odrzucenia przez bramkę node/whitelisty. Dowód dotyczy szczęśliwej ścieżki. - Restart nie leczy przyczyn źródłowych. Watchtower ma 1000+ restartów dziennie — automat
restartowałby go w kółko, maskując problem. Bez budżetu restartów (np. max 3/24 h na
serwis, potem eskalacja do
alert_only) auto-remediacja produkuje pętlę zamiast naprawy. - Otwarty defekt dispatch (niżej) w trybie automatycznym oznacza, że jedynym zabezpieczeniem
przed powtórnym wykonaniem jest marker idempotencji per
action_id. Wystarczy nowyaction_idna ten sam objaw, by restart poszedł ponownie. - Okno duplikatu (~60 s) po udanej remediacji — dziś skasowane w porę, ale to kwestia wyścigu, nie gwarancji.
_get_container_name()po cichu zwraca nazwę serwisu, gdy brakservices/<svc>/docker-compose.yml. Dla watchtowera dałoby towatchtowerzamiastpi-watchtower-1— akcja wygenerowana organicznie zakończyłaby sięfailed. W trybie automatycznym to stały szum porażek.
Warunki wstępne do poluzowania:
- Naprawa wycieku plików dispatch (niżej) — inaczej automat stoi na jednej bramce.
- Budżet restartów per serwis + eskalacja do
alert_onlypo jego wyczerpaniu. - Poluzowanie tylko dla
container_restarti tylko dla serwisów obecnych w desired state;redeployidisk_cleanupzostają w pełnym HITL.
KROK 4b — root cause crash-loopa watchtowera
Log kontenera, każde uruchomienie:
level=error msg="Error response from daemon: client version 1.25 is too old.
Minimum supported API version is 1.40, please upgrade your client to a newer version"
pi-watchtower-1 (containrrr/watchtower, obraz c352868a1654, kontener utworzony
2025-04-15) rozmawia z socketem Dockera przez API 1.25. Demon na LUSTRO wymaga minimum
1.40 i odrzuca połączenie, watchtower kończy się exit 1, restart=always uruchamia go
ponownie — cykl ~60 s. Licznik restartów kasuje się przy nocnym halt/boot, stąd
„973 restarty" to dorobek jednego dnia pracy, a nie narastająca awaria.
Co by go naprawiło (do backlogu, nie wykonane w tej sesji):
docker pull containrrr/watchtower:latest+ recreate — aktualne wydania negocjują nowsze API. Najprostsze.- Obejście:
DOCKER_API_VERSION=1.41w env kontenera. - Preferowane: usunąć watchtowera z LUSTRO. To relikt spoza GitOps, a automatyczne
podmienianie obrazów na edge'owym Pi kłóci się z modelem repo jako źródła prawdy.
Jeśli ma zostać — dopisać go do
hosts/lustro/services.yaml, bo dopiero wtedy stanie się widoczny dla supervisora.
Efekt uboczny do rozważenia niezależnie: watchtower generuje ~1440 eventów/dobę, które
nigdzie nie prowadzą, i jest głównym powodem, dla którego events/lustro/ ma 11 tys. plików.
Follow-upy
- Wyciek plików dispatch (nowy defekt, potwierdzony). Executor tworzy
actions/dispatch/<node>/z uprawnieniami 755 (aerbot:aerbot), a rsync-pull leci jakooskar(grupaaerbot) — brak prawa zapisu w katalogu, więc--remove-source-filesnie kasuje źródła. Dla porównaniadispatch/pihama 775. Dodatkowo rsync zwraca wtedy kod 23, który node-agent traktuje jako benign (returncode not in (0, 23, 24)) → cicha porażka, zero ostrzeżeń. Skutek: LUSTRO re-pulluje te same akcje co 60 s i odbija się od bramki idempotencji — w nieskończoność. Docstringpull_dispatched_actions()twierdzi, że plik jest kasowany po pobraniu; nie jest. Fix:mkdir(mode=0o775)w executorze + osobna obsługa rc=23 przy--remove-source-files. Do czasu naprawy na LUSTRO trwa zombie re-pull dwóch plików co 60 s. _get_container_name()— cichy fallback na nazwę serwisu przy brakuservices/<svc>/docker-compose.yml; produkuje akcje celujące w nieistniejące kontenery.- Okno ~60 s, w którym po udanej remediacji powstaje duplikat akcji (opóźnienie observera).
- Root cause watchtowera — patrz KROK 4b.
- Zdjęcie M1 (
NODE_TYPE=lte_node) na SOLARII i VPS — do tego czasu zero cleanupu Dockera na obu nodach. - 17 zwietrzałych akcji w
pending/z czerwca i lipca (16×alert-*,redeploy-vps-gokapi) — nikt ich nie zamyka, zaśmiecają kolejkę operatora. events/lustro/— 11 tys. plików, rosnące głównie przez watchtowera.
Pominięte / niepewne
- Bramki node-scoping i whitelisty typu potwierdzone tylko pośrednio (przez udaną egzekucję), bez testu negatywnego.
- Ścieżka porażki (
action_resultzsuccess: false) oraz timeout akcji wrunning/nie zostały przetestowane. - Gałąź prune obrazów wykonała się na zerze —
0 MB reclaimedprzy 0 dangling images przed i po. Potwierdza, że kod się wykonuje, nie że potrafi cokolwiek odzyskać. - Wycieknięte pliki dispatch na VPS usunięte ręcznie przez operatora na koniec sesji. Sam defekt (uprawnienia 755 + połknięty rc=23) pozostaje — wyciek wróci przy następnej akcji dispatchowanej na LUSTRO.
- Cykl HITL sprawdzony wyłącznie na LUSTRO. PIHA (jedyny inny node z aktywnym dispatch) nie był testowany.
Sprzątanie
prune-canary— usunięty po weryfikacji (12:29:25).prune-disposable— usunięty przez sam cleanup, zgodnie z zamysłem testu.- Obraz
alpine(ściągnięty na potrzeby kanarków) — usunięty. node-exporter— działa, podniesiony przez pipeline HITL, nie ręcznie.actions/dispatch/lustro/na VPS — opróżniony przez operatora (zombie re-pull ustał).- LUSTRO na koniec:
node-agent(healthy),node-exporter(up),piper-tts(up),pi-watchtower-1(restarting — bez zmian, świadomie).
Narrative
user-provided summary
Session 15:25
Wdrożenie do runtime dwóch fixów zmergowanych na master (supervised, checkpointy
zatwierdzane przez operatora): dispatch 0o775 + rc=23 w executorze/node-agencie
(52eca1c) oraz zdjęcie mitygacji M1 na SOLARII i VPS (1bab321). Domyka follow-upy
#1 i #5 z sesji 13:20.
Commits
Ta sesja nie wprowadziła żadnych commitów poza niniejszym logiem — deploy runtime,
zero zmian w kodzie. Wdrożone commity powstały wcześniej, na branchu
task/dispatch-perms-m1:
1bab321 revert(m1): zdjecie NODE_TYPE=lte_node na SOLARII i VPS po wdrozeniu R1
52eca1c fix(dispatch): inbox 0o775 + rsync rc=23 przestaje byc cichy
W trakcie sesji main checkout przesunął się o 75d9695 docs(recon): przyrostowka IMAP gmail + fastmail (druga sesja operatora, docs-only). Bez wpływu: node_agent.py ma to
samo sha256 aec6cb03 w obu drzewach, więc SOLARIA — zdeployowana jeszcze z 1bab321 —
nie rozjechała się z VPS-em deployowanym z 75d9695.
Files changed
Brak — drzewo robocze czyste przez całą sesję, poza tym plikiem.
Deploys
| Node | Serwis | Przed | Po | Wynik |
|---|---|---|---|---|
| SOLARIA | node-agent | node_agent.py fee079e8, obraz 438111e2 |
aec6cb03 == repo HEAD, obraz bc28a30a |
OK |
| VPS | node-agent | node_agent.py c21967d3, obraz 27be875d |
aec6cb03 == repo HEAD |
OK |
| VPS | control-plane | executor.py 5ca0490e, 4 obrazy z 2026-08-05 |
1c3b569f == repo HEAD, 4 obrazy przebudowane |
OK |
Mechanizm: scripts/deploy/deploy-service.sh --build-if-needed dla node-agenta (ta sama
ścieżka co 2026-08-05). Nie użyto scripts/deploy/deploy.sh <target>: jest to
dyspozytor Saturn-side po SSH, deployujący cały node — na VPS ruszyłby npm, outline,
joplin i ai-cluster, czyli daleko poza zakres, a sesja toczyła się z SOLARII (ssh solaria
to połączenie do samego siebie).
NODE_TYPE po zdjęciu M1: SOLARIA lte_node → ai_node (jawnie w override),
VPS lte_node → linia usunięta, NODE_TYPE="" z base compose → _resolve_node_type()
zwraca standard. Log startowy potwierdza jedno i drugie (type=ai_node, type=standard),
czyli przewidywanie z commita 1bab321 co do pustego stringa było trafne.
Gate testowy: pytest niedostępny w main checkoucie (.venv bez pytest,
~/.local/bin/pytest ma zepsuty _pytest). Oparto się na wyniku sprzed merge'a
(node-agent 70 passed, control-plane 173 passed) plus docker build obu stacków przy
deployu. Follow-up 15:25/#4.
Pierwszy cykl cleanup po zdjęciu M1
Na obu nodach marker /opt/homelab/state/last-docker-cleanup nie istniał (M1 blokował
zapis od 2026-08-04), więc _cleanup_rate_ok() zwrócił True i prune poszedł w pierwszym
cyklu, ~0,5 s po starcie — zgodnie z ostrzeżeniem w 1bab321. Dlatego kolejność w każdym
kroku była: najpierw tagi rollback, potem deploy.
SOLARIA:
INFO - node-agent starting: node=solaria type=ai_node
INFO - Pruned dangling images (0 MB reclaimed)
INFO - No disposable stopped containers (kept 0 managed)
INFO - Pruned build cache (91 MB reclaimed)
VPS:
INFO - node-agent starting: node=vps type=standard
INFO - Pruned dangling images (0 MB reclaimed)
INFO - No disposable stopped containers (kept 0 managed)
INFO - Pruned build cache (239 MB reclaimed)
Zero ubytków kontenerów na obu nodach — docker ps -a przed vs po, diff nazw pusty
(SOLARIA 9/9, VPS 24/24). humanai-mailer i humanai-landing (bez definicji w repo)
nietknięte. Tagi rollback-* przeżyły prune.
Dwie prognozy przedwdrożeniowe wymagały korekty — obie z tego samego powodu, że
docker images pokazuje rozmiar pozorny z warstwami współdzielonymi, a nie realny
odzysk:
- SOLARIA, „4 dangling ≈ 553 MB": cztery obrazy faktycznie zniknęły (35 → 32, przy
+1 nowym buildzie), ale
SpaceReclaimed= 0 MB. Ich warstwy są współdzielone z control-plane i kb-query. Realny odzysk obrazów ≈ 70 MB (17,89 → 17,82 GB); z build cache (606,1 → 510,5 MB) łącznie ≈ 165 MB. - VPS, „1 dangling 395 MB": ten obraz to żywy
outline-postgres-1— untagged, ale oznaczonyU(in use). Docker odmawia usunięcia obrazu używanego przez kontener, więc prune go nie ruszył i nie miał prawa ruszyć. Realny odzysk to wyłącznie build cache 239 MB (z 250,8 MB reclaimable).
Kontener control-plane-ui na SOLARII stoi w stanie created — poza zasięgiem prune'a
podwójnie: _prune_stopped_containers() listuje wyłącznie status=exited, a kontener ma
i tak label compose oraz restart=unless-stopped.
Weryfikacja fixu dispatch end-to-end
Stan wejściowy zdjęty przed deployem control-plane (patrz follow-up 15:25/#1 — inaczej
deploy-local.sh by go zatarł):
| ścieżka | mode | |
|---|---|---|
actions/dispatch/piha |
775 | historycznie działał |
actions/dispatch/lustro |
755 | wyciek |
actions/deploy |
755 | inbox deploy-runnera, bez podkatalogów |
Korekta do sekcji „Sprzątanie" z sesji 13:20. Zapis „actions/dispatch/lustro/ na VPS
— opróżniony przez operatora (zombie re-pull ustał)" nie odzwierciedla stanu
faktycznego: o 13:16 oba pliki (z 10:39 i 11:08) nadal leżały w źródle, a LUSTRO
re-pullowało je co 60 s aż do 13:18:23. Katalog zdrenował się dopiero w wyniku poniższego
testu — i mógł, bo dopiero wtedy miał prawa 775.
Mechanizm potwierdzony co do joty: inbox aerbot:aerbot 755, a ciągnie z niego
VPS_EVENTS_USER=oskar — uid 1002, tylko członek grupy aerbot (gid 1000). Grupa ma
r-x bez w, więc --remove-source-files nie może zrobić unlinku:
13:15:13 INFO - Action container-restart-lustro-node-exporter already processed — skipping
13:15:13 INFO - Action container-restart-lustro-watchtower already processed — skipping
13:16:16 ... (to samo) 13:17:19 ... (to samo)
Test HITL. Cel zmieniony z watchtower na node-exporter: watchtower na LUSTRO jest
w crash-loopie (137 restartów, restart=always — root cause opisany w KROKU 4b sesji
13:20), więc nie dałby czystego sygnału. Nowe action_id
(container-restart-lustro-node-exporter-dispatchfix), bo recykling starego odbiłby się od
bramki idempotencji na LUSTRO i zawiesiłby akcję w running do timeoutu.
Ścieżka: przez approved/, nie przez approval operatora. Plik pending zapisany
13:16:56 UTC został auto-anulowany przez supervisora po 7 sekundach
(drift_resolved_auto — node-exporter był zdrowy), zanim operator zdążył kliknąć. To
dokładnie zjawisko z sekcji „Obserwacja uboczna: regeneracja i auto-cancel" sesji 13:20,
tym razem szybsze (7 s vs 15 s). Zgodnie z ustaleniem przed testem druga kopia trafiła
prosto do approved/; cel testu to mechanika dispatchu, nie ścieżka approvalu. Pole
status w tej kopii zostało cancelled — bez znaczenia, executor kieruje się katalogiem,
nie polem.
Wynik — wszystkie sześć kryteriów spełnione:
- 755 → 775 dokładnie w momencie zapisu przez executora (13:17:23) ✅
- plik akcji zniknął ze źródła po pobraniu i nie wrócił w kolejnych cyklach ✅
- oba zaległe pliki zdrenowane przy okazji —
dispatch/lustro/całkowicie pusty ✅ - spam
already processed — skippingustał: dwa pełne cykle (13:19:25, 13:20:28) z zerem trafień ✅ - akcja
completedprzezaction_resultpo 70 s,node-exporterz nowym uptime ✅ - zero ERROR/WARNING w executorze, observerze, supervisorze i node-agentach — poza znanym, niezwiązanym crash-loopem watchtowera na LUSTRO ✅
13:18:23 INFO - Restarted container 'node-exporter' for action container-restart-lustro-node-exporter-dispatchfix
Czego ten test NIE dowiódł. Druga połowa fixu — klasyfikacja rc=23 z WARNING-iem — na
LUSTRO nie pojechała: node-agent tam ma nadal fee079e8 (ten sam sha, który SOLARIA miała
przed dzisiejszym deployem). grep -c "rc=23" = 0 w logach LUSTRO dlatego, że stary kod
fizycznie nie umie tego zalogować, a nie dlatego, że jest dobrze. Follow-up 15:25/#3.
Stan tagów rollback
| Node | Tag | Image ID |
|---|---|---|
| SOLARIA | node-agent:rollback-pre-dispatchfix |
438111e2 |
| SOLARIA | node-agent:rollback-pre-r1 (z 2026-08-05) |
c80a711f |
| VPS | node-agent:rollback-pre-dispatchfix |
27be875d |
| VPS | control-plane-executor:rollback-pre-dispatchfix |
b878d835 |
| VPS | control-plane-observer:rollback-pre-dispatchfix |
4b79511d |
| VPS | control-plane-supervisor:rollback-pre-dispatchfix |
357b70d1 |
| VPS | control-plane-operator-ui:rollback-pre-dispatchfix |
e522bbaa |
Pierwsze tagi rollback-* na VPS w ogóle. Otagowano wszystkie cztery obrazy control-plane,
nie tylko executora — deploy-local.sh przebudowuje cały stack, więc każdy potrzebuje celu
rollbacku.
Follow-upy (sesja 15:25)
deploy-local.shrobi rekurencyjnychown+chmodna całym/opt/homelab— do przeglądu, czy to w ogóle pożądane. Oba branche by dziś zadziałały:sudo chown -R 1000:1000(wyzwalacz:events/solaria/evt-solaria-1785946768-node_health-node.json) isudo chmod -R 775(wyzwalacze:actions/dispatch/lustro,actions/deploy). Ten drugi ustawiłbydispatch/lustrona 775 z zupełnie innego powodu niż fix w executorze, zacierając stan wejściowy testu, i nadałby 775 plikom w całym drzewie runtime (stąd-rwxrwxr-xna heartbeatach i JSON-ach akcji). Świadoma decyzja operatora: nie naprawiać ręcznie. Control-plane zdeployowano samym krokiem compose (up -d --build --force-recreate) — bez sudo, więc rozjazdy chown/chmod zostały nietknięte i czekają na przegląd. Dodatkowosudona VPS wymaga hasła, więc kanoniczna ścieżka i tak kończyłaby się handoffem (exit 5).- Lokalny control-plane na SOLARII (executor/observer/supervisor/ui,
dispatch/solaria/z 2026-07-22) ma nadal executor z bugiem0o755. Nie jest dyspozytorem floty — log to sameStarting executor loop— ale to drugi, niezdeployowany egzemplarz tego samego kodu. Osobna sesja. - LUSTRO i PIHA bez dzisiejszego node-agenta. LUSTRO:
fee079e8. Bez fixu rc=23 nadal połykają nieudany unlink — dziś to nie boli, bo executor już nie tworzy złych inboxów, ale każdy przyszły wyciek o innej przyczynie znowu będzie niewidoczny. - Brak pytest w main checkoucie na SOLARII —
deploy.sh <target>przewróciłby się na gate (exit 2) bez--no-gate. Do naprawy, zanim ktoś sięgnie po kanoniczną ścieżkę deployu z tej maszyny. - Duplikat akcji testowej —
container-restart-lustro-node-exporter-dispatchfix.jsonleży jednocześnie wcompleted/(właściwy przebieg) i wcancelled/(kopia z auto-anulowania sprzed approvalu). Pozostawione bez zmian — kosmetyka, żaden zapis produkcyjny nie był potrzebny.
Narrative
user-provided summary