# Kontenery i debug Ten dokument opisuje wdrożony kontrakt kontenerów i jawnie wskazuje pozostałe etapy. Pełne uzasadnienie, przepływ ucznia i kolejność wdrożenia są w `doc/architecture-plan.md`. Stan obecny: - `stemctl` uruchamia trzy profile przez rootless Podman; - repo środowiska zawiera wieloetapowy `Dockerfile` i Compose z dokładnie trzema usługami; gotowych obrazów nie publikujemy; - profil RP2350 zawiera oba toolchainy, Pico SDK, FreeRTOS, OpenOCD i picotool; - `rvctl`, `host` i `rv32i` pozostają aliasami okresu zgodności. ## Profile docelowe | Profil | Alias przejściowy | Service | Target Dockerfile | Targety | | --- | --- | --- | --- | --- | | `native-amd64` | `host` | `native-amd64` | `native-amd64-final` | `native` | | `hazard3-sim` | `rv32i` | `hazard3-sim` | `hazard3-sim-final` | `hazard3-baremetal`; `hazard3-freertos` po BSP timer/IRQ | | `rp2350` | brak | `rp2350` | `rp2350-final` | `rp2350-rv`, `rp2350-arm` | Wszystkie targety dziedziczą stage `dev-ui-base`. Nie jest on osobnym kontenerem roboczym. ## Jednolity kontrakt CLI ```bash stemctl env list stemctl env ensure native-amd64 stemctl env ensure hazard3-sim stemctl env ensure rp2350 stemctl build native-amd64 inf bss 1 stemctl test native-amd64 inf bss 1 stemctl run native-amd64 inf bss 1 stemctl debug native-amd64 inf bss 1 stemctl build hazard3-sim inf bss 1 --target hazard3-baremetal stemctl test hazard3-sim inf bss 1 --target hazard3-baremetal stemctl run hazard3-sim inf bss 1 stemctl debug hazard3-sim inf bss 1 stemctl build rp2350 inf bss 1 --target rp2350-rv stemctl deploy rp2350 inf bss 1 --target rp2350-rv --device /dev/bus/usb/001/006 stemctl debug rp2350 inf bss 1 --target rp2350-rv --device /dev/bus/usb/001/006 ``` Selektory `series card task` są opcjonalne, jeśli ustawiono wartości domyślne. Każda akcja obsługuje: ```text --instance NAME --target NAME --editor nvim|vim --dry-run ``` `debug` dodatkowo może przyjąć `--pane TARGET` i `--device PATH`. `deploy` przyjmuje `--device PATH` oraz `--backend probe|bootsel`. `probe list --json` ma stabilny format maszynowy, a akcje zapisują maszynowy `result.json`. `deploy` jest capability wyłącznie targetów sprzętowych RP2350. W `hazard3-sim` artefakt ładuje `run` albo `debug`; osobne `deploy` zwraca `unsupported`. ## Capabilities zamiast wyjątków w kodzie Launcher nie powinien zawierać warunków typu „jeśli profil rv32i, uruchom ten konkretny skrypt”. Profil publikuje capabilities i adaptery: ```json { "profile": "rp2350", "targets": ["rp2350-rv", "rp2350-arm"], "actions": { "build": {"requires": []}, "test": {"requires": []}, "run": {"requires": ["matching-deploy-record"]}, "debug": {"requires": ["probe"]}, "deploy": {"requires": ["probe"]}, "shell": {"requires": []} }, "debug_backends": ["openocd"], "devices": ["debug-probe", "target-usb-optional"] } ``` Karta deklaruje target, a profil dostarcza implementację. Brak capability jest normalnym, maszynowo czytelnym wynikiem `unsupported`, nie awarią launchera. ## Wspólny interfejs w kontenerze Każdy profil zapewnia kanoniczny dispatcher: ```text /usr/local/bin/stem-entry /usr/local/bin/stem-card ``` Dispatcher przyjmuje `build/test/run/debug/deploy`; manifest może zwrócić `unsupported`, jeżeli akcja nie ma sensu dla targetu. Skrypty korzystają wyłącznie z manifestu karty i zmiennych kontraktowych: ```text STEM_WORKSPACE=/workspace STEM_PROFILE=hazard3-sim STEM_TARGET=hazard3-freertos STEM_CARD=inf/bss STEM_TASK=task1 STEM_INSTANCE=hazard3-sim-inf-bss-t1 STEM_STATE_DIR=/workspace/.stem/instances/ STEM_ARTIFACT_DIR=/workspace/.stem/artifacts/// ``` Stare `RV_*` są czytane wyłącznie jako fallback w okresie migracji. ## Montowania Minimalny zestaw: | Źródło hosta | Cel | Tryb | | --- | --- | --- | | repo karty | `/workspace` | `rw` | | cache profilu | `/cache` | `rw`, osobny named volume | | `$XDG_RUNTIME_DIR/stem` | ta sama krótka ścieżka | `rw` | | konkretne urządzenie probe | urządzenie | tylko profil `rp2350` | Nie montujemy: - `$HOME/.ssh`; - pliku tokenów; - całego `/dev`; - socketu Docker/Podman; - katalogu domowego hosta; - repo innych uczniów. Repo jest własnością użytkownika hosta. Rootless Podman uruchamia kontener z mapowaniem UID/GID (`keep-id` albo równoważnym), żeby artefakty nie powstawały jako root. ## Layout tmuxa i Neovima W każdym profilu po `debug` powstaje ten sam układ: ```text work ├─ pane 0 (góra): jeden Neovim │ ├─ okno lewe: Termdebug/GDB dashboard │ └─ okno prawe: edytor źródeł i nvim-dap └─ pane 1 (dół): bash w /workspace ``` Lewa i prawa część są oknami Neovima, nie osobnymi pane'ami tmuxa. Hazard3 trzyma symulator w ukrytym oknie tmuxa `tb`. Prefix tmuxa to `Ctrl-s`. Neovim ma identyczne mapowania Termdebug i nvim-dap. Opis backendu GDB pochodzi z jednego target descriptor, dzięki czemu oba frontendy nie rozjeżdżają się. ## Debugger według profilu ### `native-amd64` - GDB jest backendem domyślnym; - LLDB pozostaje dostępny ręcznie w shellu; wspólny interfejs Termdebug używa GDB; - przed debugowaniem powstaje build `-g3 -O0`; - `test` uruchamia co najmniej ASan i UBSan. ### `hazard3-sim` - obecny codzienny backend: `fast-rsp`, jawnie opisany jako uproszczony; - docelowy, planowany backend: `fast-dm`, czyli GDB RSP -> APB/DMI -> prawdziwy DM Hazard3; - gate zgodności: OpenOCD + JTAG remote-bitbang; - planowany simulatorowy BSP FreeRTOS ma używać timera 1 kHz, czyli ticka co 150 000 cykli przy zegarze gościa 150 MHz; nie jest jeszcze wdrożony. ### `rp2350` - OpenOCD/probe dla targetów RISC-V i ARM; - picotool dla informacji, resetu i operacji wspieranych przez RP2350; - `deploy` zawsze raportuje probe, target, artefakt i jego SHA-256; - launcher nie wybiera pierwszego przypadkowego probe, gdy widocznych jest kilka urządzeń. Zewnętrzny debug probe i natywne USB płytki są rozdzielone. Standardowy debug/flash używa stabilnego probe, który nie re-enumeruje się przy resecie targetu. BOOTSEL/picotool jest opcjonalną, krótkotrwałą akcją z tego samego profilu: launcher wykrywa bieżący węzeł USB przed każdą fazą i ponownie po re-enumeracji. Nie przekazuje całego `/dev`. Build i test offline nie wymagają żadnego urządzenia. `run rp2350` wymaga zgodnego rekordu ostatniego deploy dla wybranej płytki i artefaktu. Bez niego odmawia wykonania, chyba że użytkownik jawnie zaakceptuje istniejący firmware. Zapobiega to testowaniu starego programu. ## Źródła projektu Źródła kart i projektów pobiera wyłącznie `stemctl` na hoście, zawsze pod `~/dev/workspace` danego konta Unix. Kanoniczny workspace STEM ma postać `~/dev/workspace/stem`; nie używamy do pracy katalogów kontenera ani przypadkowych klonów poza `~/dev/workspace`. Pierwszy klon launchera trafia do `~/dev/workspace/stem/tools/stem-launcher` i od niego zaczyna się cały bootstrap workspace. Typowy przepływ to: ```bash stemctl workspace sync stemctl series cards fetch inf pointers stemctl debug rp2350 inf pointers 4 ``` Katalog karty pozostaje na hoście, na przykład `~/dev/workspace/stem/series/inf/pointers/`, i jest przekazywany kontenerowi jako bind-mount `/workspace`. Nie klonujemy repozytoriów źródłowych wewnątrz kontenera: kontenery są odtwarzalne i mogą zostać usunięte, natomiast host zachowuje Git, zmiany ucznia, odpowiedzi i historię pracy. W obrazie lub osobnym cache volume mogą znajdować się wyłącznie narzędzia, zależności i odtwarzalne cache budowania. ## Instancje i sockety MCP Stabilna nazwa logiczna: ```text --- ``` Aktualny container ID jest pobierany z `podman inspect`. Pełne nazwy logiczne są w labels i registry, a sockety używają krótkich kluczy: ```text $XDG_RUNTIME_DIR/stem////t.sock $XDG_RUNTIME_DIR/stem////n.sock $XDG_RUNTIME_DIR/stem///current ``` Klucze wątku i instancji mają 12 znaków, `cid12` jest prefiksem bieżącego ID. Registry przechowuje pełne wartości. Host i kontener używają tej samej ścieżki, a całkowita długość musi być mniejsza lub równa 100 bajtów, pozostawiając zapas względem linuksowego limitu `sun_path`. Wrapper MCP otrzymuje `instance`, sprawdza label kontenera, aktualny ID i typ socketu. Dopiero wtedy łączy się z tmuxem albo Neovimem. Pozwala to Codexowi obsługiwać kilka kontenerów bez przypadkowego wejścia do starej sesji. Publiczne wejścia launchera to `stemctl mcp tmux INSTANCE` oraz `stemctl mcp nvim INSTANCE`. Hostowy wrapper wykonuje wyłącznie resolver i `podman exec`; sam serwer wraz z przypiętymi zależnościami działa wewnątrz zweryfikowanego kontenera. Neovim MCP publikuje również stan i komendy Termdebug/nvim-dap. Dla jednego stale działającego klienta hostowego dostępny jest również jawny, atomowy przełącznik obu socketów: ```bash stemctl mcp list stemctl mcp select INSTANCE_OR_CONTAINER_ID stemctl mcp status ``` `mcp select` sprawdza registry, pełny ID i label kontenera, aktywnie testuje serwery Neovima i tmuxa, a następnie jednym `rename(2)` przełącza symlink `$XDG_RUNTIME_DIR/stem/mcp-selected/current`. Jeśli choć jeden serwer nie odpowiada, poprzedni wybór pozostaje bez zmian. Katalog socketów jest tworzony przez launcher na hoście i bind-mountowany do kontenera pod identyczną ścieżką. Dopiero `nvim` i `tmux` działające w kontenerze tworzą odpowiednio `n.sock` i `t.sock`; hostowy Codex łączy się z tym samym plikiem przez widok hosta. Sockety nie są przekazywane przez sieć ani nie dają kontenerowi dostępu do socketu Podmana. Połączenia MCP mają profil `observe`, `assist` albo `full`. `full` jest przeznaczony dla nauczyciela: może wykonywać dowolne działania dostępne w Neovimie, tmuxie i terminalu wybranego kontenera jako konto ucznia. Profile `observe` i `assist` muszą używać oddzielnych, ograniczonych providerów; nie wolno udawać ograniczenia przez samą zmienną środowiskową przy providerze udostępniającym ogólny Vimscript lub polecenia pane’a. MCP nie jest czwartym kontenerem. Bridge jest częścią `dev-ui-base`, a hostowy resolver jest częścią `stem-launcher`. ## Cykl życia ```bash stemctl start hazard3-sim inf bss 1 stemctl status hazard3-sim --instance hazard3-sim-inf-bss-t1 stemctl attach hazard3-sim --instance hazard3-sim-inf-bss-t1 stemctl stop hazard3-sim --instance hazard3-sim-inf-bss-t1 stemctl rm hazard3-sim --instance hazard3-sim-inf-bss-t1 ``` `start` jest idempotentne dla zgodnej instancji. Jeżeli zmienił się lokalny ID wyniku builda albo konfiguracja targetu, launcher odtwarza kontener. `stop/start` zachowuje container ID; `rm/start` tworzy nowy ID i nowy katalog socketów. Stan karty pozostaje na hoście. ## Zdalny komputer Na komputer ucznia wchodzimy przez SSH z kluczem i uruchamiamy `stemctl` tam: ```bash ssh uczen-lab cd ~/dev/workspace/stem/tools/stem-launcher ./stemctl status hazard3-sim --instance hazard3-sim-inf-bss-t1 ./stemctl debug hazard3-sim inf bss 1 ``` GDB, UART i MCP nasłuchują na loopback albo Unix socketach. Jeżeli potrzebny jest dostęp z komputera nauczyciela, launcher generuje jawne polecenie tunelu SSH. Nie otwiera portów na `0.0.0.0`. ## Budowanie i cache `dev-ui-base` zawiera przypięte binaria/dependencies UI, lecz nie zawiera często zmienianych `configs/` i entrypointów. Trzy toolchainowe stage'e dziedziczą z niego, instalują ciężkie SDK/kompilatory, a dopiero finalne stage'e kopiują konfigurację UI i skrypty. Kod karty jest montowany, nie kopiowany. Zmiana entrypointu, dashboardu albo mapowania klawisza nie może invalidować warstwy pobierającej toolchain. ## Status implementacji | Element | Status | | --- | --- | | `native-amd64` | wdrożony i sprawdzony z ASan/UBSan | | `hazard3-sim` | wdrożony dla bare metal; `fast-dm` i BSP FreeRTOS są następne | | `rp2350` | wdrożony; FreeRTOS 1 kHz buduje ELF/UF2 dla RV i ARM, hardware gate wymaga ACL probe | | `stemctl` | wdrożony; `rvctl` jest wrapperem zgodności | | pobieranie kart z Git | istnieje w `rvctl` | | `build/test/run/debug/deploy` | wdrożony dispatcher manifestu i `result.json` | | sockety tmux/nvim | wdrożone z `thread12/instance12/container12` i resolverem | | zdalny SSH | kontrakt zaprojektowany, smoke test do dodania |