feat: add three-profile STEM container workflow

This commit is contained in:
mpabi
2026-07-14 17:49:36 +02:00
parent 9c0b833371
commit 61d00a6992
12 changed files with 1819 additions and 882 deletions
+50 -41
View File
@@ -1,8 +1,8 @@
# Agenci w środowisku kontenerowym
Ten dokument zapisuje wnioski do przyszłej integracji agentów, takich jak
Codex, Gemini i inne narzędzia asystujące. Integrację robimy po domknięciu
kontenerów `rv32i` i `host`.
Ten dokument zapisuje wnioski do integracji agentów, takich jak Codex, Claude,
Gemini i inne narzędzia asystujące. Docelowo integracja obejmuje trzy profile:
`native-amd64`, `hazard3-sim` i `rp2350`.
## Założenie
@@ -13,39 +13,40 @@ mieć wgląd w tę samą sesję, w której pracuje uczeń:
- `nvim` - edycja plików i nawigacja po kodzie,
- `gdb` lub `gdb-multiarch` - stan debuggera,
- katalog karty pracy zamontowany w kontenerze,
- stan sesji zapisany pod `.rv/<instance>/`.
- stan sesji zapisany pod `.stem/instances/<instance>/`.
Dzięki temu agent widzi środowisko debugowania, a nie tylko statyczne pliki.
## Aktualny fundament
`rv32i-hazard3-student-env` ma już elementy potrzebne do takiego modelu:
`rv32i-hazard3-student-env` dostarcza źródła wspólnego modelu:
- profil `rv32i` jako usługa `env` w `docker compose`,
- profil `host` jako osobna usługa `host`,
- profile `native-amd64`, `hazard3-sim` i `rp2350` jako trzy usługi Compose;
- jeden wieloetapowy Dockerfile budowany lokalnie, bez dystrybucji obrazów;
- `tmux` jako warstwa sesji terminalowej,
- `nvim` uruchamiany ze stabilnym socketem,
- `gdb-multiarch` dla profilu `rv32i`,
- `gdb` i opcjonalnie `lldb` dla profilu `host`,
- katalog stanu `.rv/<instance>/`,
- katalog stanu `.stem/instances/<instance>/`, z fallbackiem `.rv`;
- skrypty MCP dla `tmux` i `nvim`:
- `scripts/mcp-tmux.sh`,
- `scripts/mcp-nvim.sh`,
- `scripts/nvim-in-container.sh`.
To oznacza, że kontenery są dobrym miejscem do podłączenia agentów. Brakuje
jeszcze spójnej warstwy komend w `rvctl`.
Serwery MCP są instalowane w kontenerze, a hostowe wrappery weryfikują label i
bieżący ID Podmana przed `podman exec`. `stemctl` oraz wspólny kontrakt trzech
profili są wdrożone; osobne komendy wyższego poziomu `agent start/attach`
pozostają rozszerzeniem późniejszym.
## Docelowy model komend
Docelowo `rvctl` powinien ukrywać szczegóły socketów, kontenerów i providerów.
Docelowo `stemctl` powinien ukrywać szczegóły socketów, kontenerów i providerów.
Przykładowy kierunek:
```bash
./rvctl agent start codex rv32i inf bss 4
./rvctl agent start codex host inf bss 4
./rvctl agent start gemini rv32i inf bss 4
./rvctl agent start gemini host inf bss 4
./stemctl agent start codex native-amd64 inf bss 4
./stemctl agent start codex hazard3-sim inf bss 4
./stemctl agent start codex rp2350 inf bss 4 --target rp2350-rv
```
Skróty mogą powstać później, ale podstawowy model powinien zostać jawny:
@@ -54,28 +55,27 @@ agent, profil środowiska, seria, karta i zadanie.
Możliwy wariant dla już uruchomionej sesji:
```bash
./rvctl agent attach codex --instance rv32i-inf-bss-t4-debug
./rvctl agent attach gemini --instance host-inf-bss-t4-debug
./stemctl agent attach codex --instance hazard3-sim-inf-bss-t4
```
## Co powinien robić `rvctl`
## Co powinien robić `stemctl`
Przy `agent start` narzędzie powinno:
1. rozwiązać serię, kartę i zadanie tak samo jak `debug`,
2. wybrać profil `rv32i` albo `host`,
2. wybrać jeden z trzech profili i właściwy target,
3. nadać stabilną nazwę instancji, na przykład
`rv32i-inf-bss-t4-debug`,
`hazard3-sim-inf-bss-t4`,
4. uruchomić kontener i sesję `tmux`,
5. włączyć tryb agentowy przez zmienne środowiskowe, na przykład:
```text
RV_AGENT=codex
RV_CODEX=1
RV_INSTANCE=rv32i-inf-bss-t4-debug
STEM_AGENT=codex
STEM_MCP=1
STEM_INSTANCE=hazard3-sim-inf-bss-t4
```
6. zapisać albo odczytać sockety z `.rv/<instance>/`,
6. rozwiązać bieżący container ID i sockety z katalogu instancji,
7. uruchomić bridge MCP dla `tmux` i `nvim`,
8. przekazać agentowi minimalny kontekst:
- ścieżka repo karty,
@@ -87,51 +87,60 @@ RV_INSTANCE=rv32i-inf-bss-t4-debug
## Sockety i stan sesji
Dla każdej instancji powinniśmy konsekwentnie używać katalogu:
Dla każdej instancji używamy dwóch poziomów tożsamości:
```text
<card>/.rv/<instance>/
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/
```
W nim mogą znajdować się:
```text
tmux.sock
nvim.sock
t.sock
n.sock
gdb-sync.json
vim-mcp.sock
vim-mcp-registry.json
container.json
```
`rvctl` powinien traktować te pliki jako szczegóły implementacyjne. Użytkownik
i agent powinni dostawać komendy wyższego poziomu.
Pełna nazwa instancji pozostaje w label i registry. Krótkie klucze oraz
12-znakowy prefiks container ID utrzymują ścieżkę AF_UNIX poniżej 100 bajtów.
Aktualny ID uniemożliwia użycie socketu pozostałego po odtworzeniu kontenera.
`stemctl` traktuje te pliki jako szczegóły implementacyjne. Użytkownik i agent
dostają komendy wyższego poziomu.
## Role profili
Profil `rv32i`:
Profil `hazard3-sim`:
- debugowanie kodu dla RISC-V/Hazard3,
- `gdb-multiarch`,
- symulator,
- przykłady asemblerowe i mieszane C/ASM.
Profil `host`:
Profil `native-amd64`:
- natywne uruchomienie i debugowanie kodu C,
- szybkie testowanie algorytmów,
- `clang` albo `gcc`,
- `gdb`, opcjonalnie `lldb` i `valgrind`.
Taski czysto asemblerowe pozostają w profilu `rv32i`.
Profil `rp2350`:
- debugowanie fizycznego Pico 2/Pico 2 W;
- targety RISC-V Hazard3 i ARM Cortex-M33;
- OpenOCD, probe, flash, serial i FreeRTOS;
- dostęp tylko do jawnie wybranego urządzenia USB.
Taski czysto asemblerowe RISC-V pozostają w `hazard3-sim` albo `rp2350-rv`.
## Kolejność wdrożenia
1. Domknąć komendy kontenerowe `env`, `debug`, `run` i `shell`.
2. Ustabilizować nazwy instancji i katalog `.rv/<instance>/`.
3. Opisać kontrakt socketów dla `tmux` i `nvim`.
4. Dodać `rvctl agent list`.
5. Dodać `rvctl agent start`.
6. Dodać `rvctl agent attach`.
1. Domknąć trzy profile i komendy `build`, `test`, `run`, `debug`, `deploy`.
2. Ustabilizować labels instancji i katalogi socketów z container ID.
3. Zaimplementować resolver socketów dla `tmux` i `nvim`.
4. Dodać `stemctl agent list`.
5. Dodać `stemctl agent start`.
6. Dodać `stemctl agent attach`.
7. Dopiero potem podpinać konkretne providery: Codex, Gemini i kolejne.
## Zasada projektowa
+488
View File
@@ -0,0 +1,488 @@
# Plan architektury STEM Launcher i środowisk wykonawczych
Status: wdrożony fundament P0/P1; `fast-dm`, BSP FreeRTOS symulatora i pełne
bramki sprzętowe pozostają kolejnymi etapami
Data: 2026-07-14
Role w procesie:
- Codex — architekt i wykonawca planu;
- Claude — senior reviewer;
- użytkownik — właściciel produktu i programu nauczania.
## 1. Decyzja architektoniczna
System składa się z jednego launchera uruchamianego na hoście, jednego wspólnego
stage'a bazowego Dockerfile oraz dokładnie trzech kontenerów roboczych:
| Profil launchera | Target Dockerfile | Rola |
| --- | --- | --- |
| `native-amd64` | `native-amd64-final` | szybkie testy algorytmów C/C++ na AMD64 |
| `hazard3-sim` | `hazard3-sim-final` | RV32 bare metal i FreeRTOS na rzeczywistym RTL Hazard3 w Verilatorze |
| `rp2350` | `rp2350-final` | fizyczny Pico 2/Pico 2 W: RISC-V Hazard3 i ARM Cortex-M33 |
Stage `dev-ui-base` jest wspólną warstwą budowania, a nie czwartym stale
uruchomionym kontenerem. Hoverboard nie otrzymuje teraz osobnego kontenera.
Dodamy go dopiero po ustaleniu mikrokontrolera, BSP i interfejsu probe.
Nie tworzymy osobnych kontenerów dla:
- bare metal i FreeRTOS;
- RP2350 RISC-V i RP2350 ARM;
- C i C++/STL;
- Termdebug, nvim-dap, tmuxa ani MCP.
Są to targety, tryby albo narzędzia wewnątrz jednego z trzech profili.
## 2. Granice odpowiedzialności
### Host i `stem-launcher`
Launcher działa jako zwykły użytkownik hosta i odpowiada za:
1. synchronizację publicznego manifestu serii;
2. listowanie serii, kart i zadań;
3. pobieranie kodu karty z Git do workspace na hoście;
4. przygotowanie repo odpowiedzi, brancha ucznia i remotes;
5. wybór profilu oraz targetu;
6. lokalny build właściwego targetu ze źródłowego Dockerfile albo użycie jego
lokalnego cache;
7. utworzenie, wznowienie, zatrzymanie i usunięcie kontenera;
8. wywołanie `build`, `test`, `run`, `debug` i `deploy`;
9. rejestrację artefaktów, logów, wyników i socketów MCP;
10. wykrywanie probe i przekazanie wyłącznie potrzebnego urządzenia do profilu
`rp2350`.
Klucze SSH, tokeny Gitea i konfiguracja Git pozostają na hoście. Prywatnego
klucza SSH nie montujemy do kontenera. Kontenery nie otrzymują socketu Podmana
ani Dockera i nie zarządzają innymi kontenerami.
### Kontener roboczy
Kontener odpowiada wyłącznie za powtarzalne środowisko kompilacji,
uruchamiania i debugowania. Otrzymuje:
- repo karty pod `/workspace` jako bind mount;
- katalog stanu instancji pod `/workspace/.stem/instances/<instance>`;
- deklarację targetu i akcji;
- opcjonalnie konkretne urządzenie USB dla RP2350;
- katalog wymiany socketów MCP widoczny również na hoście.
Kod źródłowy i artefakty pozostają na hoście. Usunięcie kontenera nie usuwa
pracy ucznia.
## 3. Przepływ ucznia
Docelowy przepływ jest jeden dla domu, pracowni i zdalnego komputera:
```bash
stemctl workspace sync
stemctl series list
stemctl series cards fetch inf bss
stemctl env ensure native-amd64
stemctl test native-amd64
stemctl run native-amd64
stemctl env ensure hazard3-sim
stemctl test hazard3-sim
stemctl debug hazard3-sim
stemctl env ensure rp2350
stemctl probe list
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
```
Pełne selektory pozostają dostępne, na przykład:
```bash
stemctl test hazard3-sim inf bss 1
stemctl deploy rp2350 inf bss 1 --target rp2350-arm
```
Launcher wykonuje następujący pipeline:
```text
workspace-info
wybór serii/karty/zadania
clone/fetch Git na hoście ──► branch/remotes odpowiedzi
wybór profilu i targetu
rootless Podman + bind mount repo
├──► build ─► test ─► run/debug
└──► artefakt ─► deploy, tylko gdy target to obsługuje
```
### Znaczenie akcji
| Akcja | `native-amd64` | `hazard3-sim` | `rp2350` |
| --- | --- | --- | --- |
| `build` | binarka ELF hosta | ELF/BIN RV32 | ELF/UF2 dla RV albo ARM |
| `test` | golden tests, ASan/UBSan | te same wektory, RV32/RTL | testy na płytce, gdy karta je deklaruje |
| `run` | proces natywny | program w Verilatorze | uruchomienie wcześniej wgranego programu i serial/RTT |
| `debug` | GDB/LLDB | obecnie `fast-rsp`, docelowo `fast-dm`, kontrolnie JTAG | GDB przez OpenOCD/probe |
| `deploy` | nieobsługiwane | nieobsługiwane; artefakt ładuje `run`/`debug` | flash przez probe |
Komenda nie może udawać sukcesu dla niewspieranej możliwości. Launcher czyta
capabilities profilu i zwraca czytelny komunikat, na przykład
`deploy is not supported by native-amd64`.
`run rp2350` nie wykonuje ukrytego flashowania. Wymaga rekordu, że wybrany
artefakt został wcześniej wdrożony na wybraną płytkę; w przeciwnym razie
odmawia startu albo wymaga jawnego `--allow-existing-firmware`. Artefakt
powinien zawierać build ID możliwy do potwierdzenia przez UART/RTT lub metadata
RP2350, aby nie testować przypadkiem starego firmware.
## 4. Kontrakt karty pracy
Karta nie może zależeć od zaszytych w launcherze nazw plików. Jej wersjonowany
manifest powinien deklarować co najmniej:
```yaml
schema: 1
card: inf/bss
default_task: task1
profiles:
native-amd64:
default_target: native
hazard3-sim:
default_target: hazard3-baremetal
rp2350:
default_target: rp2350-rv
targets:
native:
profile: native-amd64
actions: [build, test, run, debug]
hazard3-baremetal:
profile: hazard3-sim
runtime: baremetal
actions: [build, test, run, debug]
hazard3-freertos:
profile: hazard3-sim
runtime: freertos
tick_hz: 1000
actions: [build, test, run, debug]
rp2350-rv:
profile: rp2350
arch: riscv
actions: [build, test, run, debug, deploy]
requires:
debug: [probe]
deploy: [probe]
rp2350-arm:
profile: rp2350
arch: arm
actions: [build, test, run, debug, deploy]
requires:
debug: [probe]
deploy: [probe]
actions:
build: ./tools/card build
test: ./tools/card test
run: ./tools/card run
debug: ./tools/card debug
deploy: ./tools/card deploy
artifacts:
directory: .stem/artifacts
```
Nazwa pliku manifestu i dokładny schemat zostaną zatwierdzone w implementacji.
Ważny jest podział: `workspace-info` mówi, skąd pobrać kartę, manifest karty
mówi, jak ją wykonać, a profil środowiska dostarcza narzędzia.
Każda akcja zapisuje maszynowo czytelny wynik do:
```text
.stem/results/<profile>/<target>/<task>/<run-id>/result.json
```
Wynik zawiera co najmniej commit karty, lokalny identyfikator wyniku builda,
wersję toolchainu,
polecenie, kod wyjścia, czas, testy i skróty artefaktów. Dzięki temu porównanie
AMD64 → Hazard3 → RP2350 nie opiera się na ręcznym oglądaniu terminala.
## 5. Trzy kontenery
### 5.1 `native-amd64`
Cel: najszybsza pętla dla algorytmów z podręcznika, matury i ZPE.
Narzędzia specyficzne:
- GCC i Clang;
- GDB i opcjonalnie LLDB;
- ASan, UBSan, coverage i Valgrind;
- CMake, Ninja i Make;
- testy jednostkowe C/C++.
Profil nie zawiera SDK RP2350 ani Verilatora. Testuje algorytm i kontrakt API,
nie emuluje zachowania 32-bitowego MCU. Testy przenośności muszą dodatkowo
przejść w `hazard3-sim`.
### 5.2 `hazard3-sim`
Cel: praca domowa bez płytki i dydaktyczny wgląd w procesor.
Narzędzia specyficzne:
- przypięty toolchain `riscv64-unknown-elf`;
- przypięty Verilator;
- Hazard3 RTL oraz testbench;
- runtime bare-metal i osobny BSP symulatora dla FreeRTOS;
- szybki RSP oraz planowany transport `fast-dm` do wbudowanego DM Hazard3;
- OpenOCD/remote-bitbang wyłącznie jako okresowy gate zgodności;
- opcjonalne VCD i narzędzia do zajęć z Veriloga.
Dla FreeRTOS 1 kHz timer symulatora zwiększa `mtime` dokładnie raz na cykl.
Przy deklarowanym zegarze 150 MHz tick przypada co 150 000 cykli RTL. Jest to
1 kHz czasu gościa, nie obietnica czasu rzeczywistego hosta. Kod aplikacyjny i
wektory testowe są wspólne z RP2350, natomiast BSP, startup i linker są osobne.
Bieżący port FreeRTOS RP2350 ustawia `SIO MTIME` w tryb `FULLSPEED`, dlatego
przy zegarze systemowym 150 MHz również używa 150 000 zliczeń na tick. Nie
traktujemy jednak tej stałej jako wspólnego API BSP: profil sam wylicza ją z
rzeczywistego zegara i trybu timera.
Testy czasowe mają dwa poziomy. Smoke test PR wykonuje co najmniej 100 ticków z
produkcyjnym dzielnikiem 150 000. Długi test schedulera wykonuje 10 000 ticków
na jawnie przyspieszonym zegarze testowym i nie jest dowodem częstotliwości.
Nightly/release może wykonać dłuższy test produkcyjnego dzielnika w ustalonym
budżecie czasu.
### 5.3 `rp2350`
Cel: pracownia z fizycznym Pico 2/Pico 2 W.
Narzędzia specyficzne:
- przypięty Pico SDK i FreeRTOS Kernel;
- toolchain RISC-V dla Hazard3;
- toolchain ARM dla Cortex-M33;
- CMake/Ninja;
- OpenOCD zgodny z RP2350, picotool i obsługa wybranego probe;
- serial/RTT oraz reguły diagnostyczne USB.
Target `rp2350-rv` i `rp2350-arm` wybiera konfigurację CMake, startup, linker,
GDB i backend OpenOCD. Nie wymaga kolejnego Dockerfile. Bare metal i FreeRTOS są wariantami
manifestu projektu, nie oddzielnymi kontenerami.
Kontener działa rootless, bez `--privileged`. Launcher przekazuje tylko jawnie
wybrane urządzenia. Host otrzymuje regułę udev lub ACL, która pozwala grupie
laboratoryjnej korzystać z probe bez `sudo`.
Debug probe i natywne USB płytki to dwie różne capabilities. Domyślny
debug/deploy przez zewnętrzny probe używa stabilnego urządzenia probe; reset
targetu nie powinien go odłączać. Opcjonalny BOOTSEL/picotool powoduje
re-enumerację targetu i zmianę adresu USB. Taki deploy jest osobną, krótkotrwałą
akcją z tego samego profilu `rp2350`: launcher ponownie wykrywa urządzenie po
każdej fazie i uruchamia akcję z aktualnym węzłem. Nie przekazujemy całego
`/dev`; jeżeli backend wymaga dostępu na poziomie portu/busa USB, zakres jest
jawnie raportowany i zatwierdzany dla tej instancji.
## 6. Wspólny kontrakt UI
Każdy profil dziedziczy z `dev-ui-base`:
- tmux z prefiksem `Ctrl-s`;
- Neovim i identyczny zestaw skrótów;
- Termdebug, nvim-dap i dashboard GDB;
- serwery/bridge MCP dla tmuxa i Neovima;
- `ripgrep`, `fd`, Git, Python i podstawowe narzędzia diagnostyczne;
- użytkownika bez roota i ten sam katalog roboczy `/workspace`.
Domyślny układ jednego widocznego okna tmuxa:
```text
┌────────────────────────────────────────────────────────────┐
│ Neovim — jeden pane tmuxa │
│ ┌─ Termdebug + GDB dashboard ─┬─ edytor/nvim-dap ───────┐ │
│ └──────────────────────────────┴──────────────────────────┘ │
├────────────────────────────────────────────────────────────┤
│ bash, cwd=/workspace — drugi pane tmuxa │
└────────────────────────────────────────────────────────────┘
```
Termdebug i edytor są oknami jednego Neovima. Hazard3 uruchamia symulator w
drugim, domyślnie ukrytym oknie tmuxa `tb`.
Termdebug i nvim-dap są dwiema nakładkami na ten sam opis targetu GDB. Karta
nie przechowuje osobnych adresów i poleceń dla każdego frontendu.
## 7. Tożsamość instancji i MCP
Każda sesja ma stabilną tożsamość logiczną, na przykład:
```text
hazard3-sim-inf-bss-t1
```
Kontener otrzymuje label `edu.stem.instance`. Aktualny identyfikator Podmana
jest tożsamością procesu. Pełne nazwy pozostają w labels/registry, natomiast
ścieżka Unix socketu używa krótkich kluczy, aby zmieścić się w limicie
`sun_path` (maksymalnie 107 użytecznych bajtów na Linuksie):
```text
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/t.sock
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/n.sock
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/current
```
`thread-key` i `instance-key` są 12-znakowymi skrótami kryptograficznymi, a
`cid12` to jednoznaczny na danym hoście prefiks bieżącego container ID. Registry
mapuje je na pełne wartości i jest weryfikowane przez `podman inspect`. Ta sama
krótka ścieżka jest widoczna na hoście i w kontenerze. Test startowy odrzuca
konfigurację przekraczającą budżet 100 bajtów.
Wrapper MCP wybiera najpierw jawną instancję, przez `podman inspect` sprawdza
jej aktualny container ID, a dopiero potem otwiera socket. `current` jest
wskaźnikiem pomocniczym ograniczonym do wątku agenta, nie globalnym wyborem dla
wszystkich kontenerów. Stary socket nie może zostać użyty po odtworzeniu
kontenera.
Agent widzi dokładnie tę samą sesję tmuxa i Neovima co uczeń. MCP nie jest
osobnym kontenerem.
## 8. Rootless Podman i SSH
Podman jest domyślnym runtime. Docker może pozostać przejściowym backendem dla
istniejących komputerów, ale kontrakt nie może wymagać członkostwa ucznia w
grupie `docker`.
Na zdalnym komputerze ucznia nauczyciel lub agent:
1. łączy się wyłącznie przez SSH z parą kluczy;
2. uruchamia `stemctl` na tym zdalnym hoście;
3. tamtejszy launcher zarządza lokalnym rootless Podmanem i lokalnym
workspace;
4. w razie potrzeby sockety MCP/GDB są udostępniane przez tunel SSH, nigdy
przez nasłuch na `0.0.0.0`.
Nie montujemy zdalnego katalogu przez SSHFS jako podstawowego modelu i nie
przekazujemy prywatnego klucza do kontenera. Repo i kontener mają znajdować się
na tym samym hoście wykonawczym.
## 9. Budowanie kontenerów ze źródeł i cache
Repo przechowuje jeden wieloetapowy `Dockerfile` i pliki Compose. Nie
przechowuje ani nie publikuje gotowych obrazów. `dev-ui-base` zawiera
przypięte binaria i zależności UI, ale nie zawiera często zmienianych
`configs/` ani entrypointów. Graf stage'ów:
```text
os-base
└─ dev-ui-base (tmux/nvim/MCP i przypięte zależności, bez configs/)
├─ ui-node-deps (przypięte zależności serwerów MCP)
├─ native-toolchain
│ └─ native-amd64-final + COPY configs/ i entrypointów
├─ hazard3-toolchain
│ └─ hazard3-sim-final + COPY configs/ i entrypointów
└─ rp2350-toolchain
└─ rp2350-final + COPY configs/ i entrypointów
```
Duże toolchainy, SDK, Verilator, Hazard3 i OpenOCD są instalowane w stage'ach
toolchain przed skopiowaniem konfiguracji UI. Zmiana lockfile zależności UI
może celowo przebudować potomków; zwykła zmiana mapowania klawisza, dashboardu
albo entrypointu nie może tego zrobić.
Kod karty nigdy nie jest kopiowany do warstw kontenera. Zmiana konfiguracji końcowej nie
może ponownie pobierać toolchainów. W CI sprawdzamy drugi build bez zmian i
raportujemy wykorzystanie cache.
Uczeń wykonuje `stemctl env ensure`, które używa lokalnego cache lub buduje
profil ze źródłowego Dockerfile. Nic nie jest pobierane z rejestru obrazów.
## 10. Migracja `rv-launcher` do `stem-launcher`
Nazwa RV jest zbyt wąska, ponieważ launcher obsługuje serie informatyczne,
fizyczne, AMD64, RISC-V i ARM. Docelowe nazwy to:
- repo: `edu-tools/stem-launcher`;
- publiczne CLI: `stemctl`;
- workspace: `~/dev/workspace/stem`;
- zmienne: `STEM_*`;
- stan karty: `.stem/`.
Migracja musi zachować działające workspace uczniów:
1. dodać `stemctl` jako nowy entrypoint do obecnej implementacji;
2. akceptować `rvctl` jako wrapper kompatybilności z ostrzeżeniem;
3. czytać nowe `STEM_*`, a następnie stare `RV_*` jako fallback;
4. domyślnie używać `~/dev/workspace/stem`, ale automatycznie wykrywać
istniejące `~/dev/workspace/rv`;
5. zmigrować `.rv/` do `.stem/` bez usuwania stanu;
6. dopiero wtedy zmienić nazwę repo na Gitea i zaktualizować manifest;
7. pozostawić alias lub repo informacyjne pod starą nazwą na co najmniej jeden
cykl zajęć;
8. usunąć kompatybilność dopiero po telemetrycznym/audytowym potwierdzeniu, że
materiały nie wywołują starych nazw.
Szczegóły i kryteria wycofania opisuje `doc/migration-stem-launcher.md`.
## 11. Kolejność wdrożenia
### P0 — kontrakt i bezpieczeństwo
1. Zatwierdzić nazwy profili, capabilities i schemat manifestu karty.
2. Dodać `stemctl` oraz zgodność `rvctl`.
3. Przenieść runtime z Docker Compose na abstrakcję z rootless Podmanem jako
backendem domyślnym.
4. Rozdzielić sekrety hosta od kontenerów i usunąć tokeny z URL-i remotes po
operacji Git.
5. Ustabilizować tożsamość instancji, labels i sockety MCP.
### P1 — trzy targety jednego Dockerfile
1. Wyodrębnić `dev-ui-base`.
2. Przenieść istniejący profil `host` do `native-amd64`.
3. Przenieść istniejący `rv32i` do `hazard3-sim` i dodać simulatorowy BSP
FreeRTOS 1 kHz.
4. Zbudować `rp2350` z targetami RV i ARM oraz bezpiecznym przekazywaniem probe.
5. Ujednolicić layout tmuxa, Termdebug, DAP i MCP.
### P2 — pełny pipeline karty
1. Zaimplementować manifest karty i akcje `build/test/run/debug/deploy`.
2. Zapisywać wyniki i metadane artefaktów.
3. Porównywać golden tests pomiędzy trzema profilami.
4. Dodać `fast-dm` oraz okresowy gate JTAG.
5. Dodać zdalny smoke test przez SSH bez otwierania portów debuggera.
## 12. Bramki akceptacyjne
| Obszar | Kryterium |
| --- | --- |
| Fetch | czysty host pobiera manifest i kartę wyłącznie przez `stemctl` |
| Persistence | usunięcie kontenera nie zmienia ani nie usuwa repo ucznia |
| Native | golden tests, ASan i UBSan przechodzą na AMD64 |
| Emulator | te same wektory przechodzą jako RV32 bare metal i FreeRTOS |
| FreeRTOS | co najmniej 100 ticków z produkcyjnym dzielnikiem 150 000 oraz 10 000 ticków przyspieszonego testu schedulera bez zgubionego cyklu |
| RP2350 RV | build, flash, reset, UART i debug przez probe przechodzą bez `sudo` |
| RP2350 ARM | ten sam zestaw operacji przechodzi w tym samym obrazie |
| USB | probe pozostaje dostępny po resecie; BOOTSEL/picotool przechodzi kontrolowaną re-enumerację bez `--privileged` |
| UI | layout tmux/Nvim, skróty i dashboard są identyczne w trzech profilach |
| MCP | agent steruje wskazaną instancją i nie łączy się ze starym socketem |
| Isolation | brak socketu runtime, prywatnych kluczy i `--privileged` w kontenerze |
| Reproducibility | wynik zapisuje commit karty, lokalny ID builda i wersje toolchainów |
| SSH | praca zdalna działa przez klucze i tunele na loopback, bez publicznego GDB |
| Cache | zmiana entrypointu lub konfiguracji UI nie powoduje ponownego pobrania toolchainów |
| Compatibility | stare `rvctl` i workspace `/rv` działają w okresie migracji |
## 13. Poza zakresem tej iteracji
- czwarty kontener dla hoverboardu;
- orkiestracja klastra albo Kubernetes;
- przechowywanie pracy ucznia wewnątrz warstwy kontenera;
- udostępnianie GDB, UART lub MCP na publicznym interfejsie;
- automatyczne podawanie agentowi prywatnych kluczy i tokenów;
- gwarancja działania symulacji RTL w czasie rzeczywistym.
+250 -129
View File
@@ -1,162 +1,283 @@
# Kontenery i debug
`rvctl` integruje workspace z narzędziem `rv32i-hazard3-student-env`.
Środowisko ma dwa profile kontenerów:
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`.
- `rv32i` - docelowe środowisko RISC-V/Hazard3 z `nvim`, `tmux`,
`gdb-multiarch`, toolchainem RISC-V i symulatorem Hazard3.
- `host` - natywne środowisko na architekturze hosta z `clang`, `gcc`, `gdb`,
opcjonalnie `lldb`, `valgrind`, `nvim` i `tmux`.
Stan obecny:
Profile są uruchamiane jako oddzielne usługi `docker compose`, więc zależności
RISC-V i zależności natywnego debugowania nie mieszają się ze sobą.
- `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.
## Szybki przepływ
## 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
./rvctl env list
./rvctl env sync
./rvctl env build rv32i
./rvctl env build host
stemctl env list
stemctl env ensure native-amd64
stemctl env ensure hazard3-sim
stemctl env ensure rp2350
./rvctl debug rv32i inf bss 1
./rvctl debug host inf bss 1
./rvctl run host inf bss 1
./rvctl shell rv32i inf bss
./rvctl shell host inf bss
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
```
Jeżeli `workspace.json` ma ustawione domyślne wartości `defaults.series`,
`defaults.card` i `defaults.task`, można używać krótszych komend:
```bash
./rvctl debug rv32i
./rvctl debug host
./rvctl debug rv32i 4
./rvctl debug host 4
```
## `env list`
Pokazuje dostępne profile i bieżący katalog narzędzia:
```bash
./rvctl env list
```
Typowy wynik:
Selektory `series card task` są opcjonalne, jeśli ustawiono wartości domyślne.
Każda akcja obsługuje:
```text
profile service image status purpose
rv32i env edu-inf/rv32i-hazard3-env:latest ready RV32I/Hazard3 nvim + gdb-multiarch
host host edu-inf/rv32i-hazard3-host-env:latest ready native host clang/gcc + gdb/lldb
--instance NAME
--target NAME
--editor nvim|vim
--dry-run
```
## `env sync`
`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`.
Klonuje albo aktualizuje repo środowiska do:
`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
~/dev/workspace/rv/tools/rv32i-hazard3-student-env
/usr/local/bin/stem-entry
/usr/local/bin/stem-card
```
Komenda:
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/<instance>
STEM_ARTIFACT_DIR=/workspace/.stem/artifacts/<profile>/<target>/<task>
```
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.
## Instancje i sockety MCP
Stabilna nazwa logiczna:
```text
<profile>-<series>-<card>-<task>
```
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/<thread-key>/<instance-key>/<cid12>/t.sock
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/n.sock
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/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.
MCP nie jest czwartym kontenerem. Bridge jest częścią `dev-ui-base`, a hostowy
resolver jest częścią `stem-launcher`.
## Cykl życia
```bash
./rvctl env sync
./rvctl env sync --dry-run
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
```
Jeżeli tego repo jeszcze nie ma w workspace, `rvctl` używa fallbacku z
`~/dev/edu/repos/rv/rv32i-hazard3-env`, o ile jest dostępny.
`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.
## `env build`
## Zdalny komputer
Buduje wybrany profil kontenera:
Na komputer ucznia wchodzimy przez SSH z kluczem i uruchamiamy `stemctl` tam:
```bash
./rvctl env build rv32i
./rvctl env build host
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
```
Wariant `--dry-run` pokazuje dokładną komendę bez uruchamiania Dockera:
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`.
```bash
./rvctl env build host --dry-run
```
## Budowanie i cache
## `debug`
`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.
Uruchamia debugowanie zadania w wybranym profilu.
## Status implementacji
```bash
./rvctl debug rv32i inf bss 1
./rvctl debug host inf bss 1
```
W profilu `rv32i` startuje środowisko Hazard3: symulator, `gdb-multiarch` i
`nvim` w sesji `tmux`.
W profilu `host` wybrane źródło C jest kompilowane natywnie z debug info, a
potem uruchamiane w `gdb` obok `nvim`.
Ten profil jest przeznaczony dla tasków C z funkcją `main`; taski czysto
asemblerskie nadal debugujemy w profilu `rv32i`.
Przydatne warianty:
```bash
./rvctl debug rv32i bss 4
./rvctl debug host bss 4
./rvctl debug host 4 --editor vim
./rvctl debug host 4 --instance host-bss-t4
./rvctl debug rv32i 4 --pane 0
./rvctl debug rv32i 4 pane node:0
./rvctl debug host 4 --dry-run
```
`--pane TARGET` oraz forma `pane TARGET` wysyłają wygenerowaną komendę do
istniejącego panelu tmuxa na hoście przez `tmux send-keys`. Skróty targetów:
- `0` - panel `0` w bieżącym oknie,
- `node:0` - okno `node`, panel `0`,
- `%3` albo `:node.0` - natywny target tmuxa.
## `run`
Na razie `run` jest wdrożone dla profilu `host`. Buduje wybrany task natywnie i
uruchamia wynikowy program:
```bash
./rvctl run host inf bss 1
./rvctl run host 4
```
Dla profilu `rv32i` używamy obecnie `debug rv32i`, bo przepływ RISC-V zakłada
symulator Hazard3 i GDB.
## `shell`
Otwiera shell w wybranym profilu kontenera z podmontowaną kartą:
```bash
./rvctl shell rv32i inf bss
./rvctl shell host inf bss
```
## Selektory
Komendy `debug`, `run` i `shell` używają tych samych skrótów co `tasks`:
```bash
./rvctl debug host 1
./rvctl debug host bss 1
./rvctl debug host inf bss 1
```
`rvctl` rozwiązuje `1` do właściwego zadania, na przykład
`task1_bss`, na podstawie plików w `src/tasks`.
## Starsza komenda
`tmux-container` zostaje jako alias kompatybilności dla starszego trybu
shellowego. Nowe przykłady powinny używać `debug`, `run`, `shell` i `env`.
| 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 |
+109
View File
@@ -0,0 +1,109 @@
# Migracja `rv-launcher` do `stem-launcher`
Status: M1M3 wdrożone lokalnie; M4 (zmiana nazwy zdalnego repo) celowo
odłożone do zakończenia okresu zgodności
Data: 2026-07-14
## Dlaczego zmieniamy nazwę
Launcher obsługuje już kod natywny AMD64 i Hazard3, a plan obejmuje RP2350
RISC-V, RP2350 ARM, fizykę i kolejne serie STEM. Nazwy `rv-launcher`, `rvctl`,
`RV_*` i `~/dev/workspace/rv` błędnie sugerują narzędzie ograniczone do RISC-V.
Docelowy słownik:
| Obecnie | Docelowo |
| --- | --- |
| `rv-launcher` | `stem-launcher` |
| `rvctl` | `stemctl` |
| `rvctl.py` | wewnętrzny moduł launchera; nazwa nie jest publicznym API |
| `RV_*` | `STEM_*` |
| `.rv/` | `.stem/` |
| `~/dev/workspace/rv` | `~/dev/workspace/stem` |
| profil `host` | `native-amd64` |
| profil `rv32i` | `hazard3-sim` |
## Zasada migracji
Najpierw dostarczamy zgodność w kodzie, a dopiero później zmieniamy nazwę repo
na serwerze. Odwrotna kolejność zepsułaby bootstrap, token records, remotes,
ścieżki dokumentacji i istniejące workspace uczniów.
## Etapy
### M1 — alias CLI (wdrożone)
- dodać wykonywalny `stemctl` wskazujący tę samą implementację;
- pozostawić `rvctl` jako wrapper;
- pomoc i nowe materiały pokazują wyłącznie `stemctl`;
- `rvctl` drukuje jednorazowe ostrzeżenie o wycofaniu na stderr, ale zachowuje
format stdout potrzebny skryptom.
### M2 — konfiguracja i ścieżki (wdrożone)
- dodać wersjonowaną migrację `workspace.json`;
- nowe zmienne `STEM_*` mają pierwszeństwo, stare `RV_*` są fallbackiem;
- jeśli istnieje `/workspace/.stem`, użyć go;
- jeśli istnieje tylko `/workspace/.rv`, użyć go i zaproponować migrację;
- komenda `stemctl workspace doctor` pokazuje użyte ścieżki i runtime;
- komenda `stemctl workspace migrate --dry-run` nie zmienia danych, tylko
pokazuje plan.
Migracja katalogu stanu musi być atomowa na pojedynczym filesystemie i nigdy
nie usuwa starego katalogu przed zweryfikowaniem nowego.
### M3 — profile i kontenery (wdrożone)
- wprowadzić `native-amd64`, `hazard3-sim`, `rp2350`;
- zachować aliasy `host -> native-amd64` oraz `rv32i -> hazard3-sim`;
- kontenery otrzymują labels z nazwą kanoniczną, nie aliasem;
- wyniki zapisują nazwę kanoniczną oraz opcjonalne `requested_alias`.
### M4 — repo Gitea (odłożone)
Po wydaniu kompatybilnego launchera:
1. utworzyć lub zmienić nazwę na `edu-tools/stem-launcher`;
2. zaktualizować `workspace-info`, bootstrap i token records;
3. sprawdzić clone/fetch/push przez nowe URL;
4. pozostawić pod `edu-tools/rv-launcher` przekierowanie albo małe repo z
komunikatem migracyjnym, zależnie od możliwości Gitea;
5. nie usuwać starej nazwy podczas trwającego semestru.
Zmiana repo na Gitea jest operacją administracyjną i nie jest wykonywana przez
samą aktualizację dokumentacji.
### M5 — wycofanie kompatybilności
Stare nazwy można usunąć dopiero, gdy:
- wszystkie serie w `workspace-info` wskazują nowe CLI i repo;
- CI nie używa `rvctl`, `RV_*`, `.rv` ani profili `host`/`rv32i`;
- obraz pracowni został odświeżony;
- co najmniej jeden pełny cykl zajęć przeszedł na `stemctl`;
- dostępna jest instrukcja ręcznego odzyskania starego workspace.
## Testy migracji
1. Nowa instalacja bez istniejącego workspace.
2. Istniejący workspace `/rv` z kartami i bez aktywnych kontenerów.
3. Workspace z `.rv/<instance>` i zatrzymanym kontenerem.
4. Store tokenów wskazujący repo `rv-launcher`.
5. Remotes bez sekretu oraz remotes z historycznym tokenem w URL.
6. Równoległe instancje profili `host` i `rv32i`.
7. Ponowne wykonanie migracji — wynik musi być idempotentny.
8. Rollback konfiguracji bez utraty repo, branchy, artefaktów i logów.
## Kryterium zakończenia
Uczeń po migracji wykonuje:
```bash
stemctl series cards fetch inf bss
stemctl test native-amd64 inf bss 1
stemctl debug hazard3-sim inf bss 1
stemctl deploy rp2350 inf bss 1 --target rp2350-rv --device /dev/bus/usb/001/006
```
Istniejący skrypt używający `rvctl debug rv32i` nadal działa w zadeklarowanym
okresie kompatybilności i trafia do tego samego kanonicznego profilu.
+150
View File
@@ -0,0 +1,150 @@
# Senior review Claude — 2026-07-14
Zakres recenzji:
- `stem-launcher`: plan architektury, kontenery, agenci i migracja nazw;
- `rv32i-hazard3-student-env`: plan ewaluacji, Containerfile/Dockerfile,
Compose, integracja launchera i specyfikacja trzech obrazów;
- zgodność planu z bieżącym `rvctl.py` i `workspace.json`.
## Werdykt
Claude wydał akceptację warunkową. Zaakceptował:
- dokładnie trzy kontenery robocze i wspólną warstwę UI;
- profile `native-amd64`, `hazard3-sim`, `rp2350` oraz targety wewnątrz
profilu;
- Git, klucze i tokeny wyłącznie na hoście;
- rootless Podmana bez socketu runtime i bez `--privileged`;
- capabilities i wynik `unsupported` zamiast warunków zaszytych w launcherze;
- migrację zgodności przed zmianą nazwy repo Gitea;
- `fast-rsp` jako obecny backend, `fast-dm` jako kierunek i JTAG jako gate;
- tę samą sesję tmux/Neovim dla ucznia i agenta.
## Findings i rozstrzygnięcia
### P1 — graf cache Containerfile
Problem: częsta konfiguracja UI znajdowała się w stage'u będącym przodkiem
ciężkich toolchainów. Jej zmiana przebudowałaby SDK i Verilator.
Rozstrzygnięcie: `dev-ui-base` zawiera przypięte binaria/dependencies UI, ale
nie `configs/` ani entrypointy. Toolchainowe stage'e dziedziczą z niego, a
konfiguracja jest kopiowana dopiero do finalnych stage'ów. Test cache obejmuje
zmianę entrypointu i konfiguracji UI.
### P1 — limit Unix socketów
Problem: pełne `thread/instance/container-id` w ścieżce workspace przekraczały
linuksowy limit `sun_path`.
Rozstrzygnięcie:
```text
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/t.sock
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/n.sock
```
Pełne wartości są w registry/labels. Budżet ścieżki wynosi 100 bajtów.
### P1 — `deploy hazard3-sim`
Problem: jeden dokument utożsamiał deploy z załadowaniem symulatora, pozostałe
nie definiowały tej operacji.
Rozstrzygnięcie: `deploy` dla `hazard3-sim` jest `unsupported`. `run` i `debug`
ładują artefakt. Deploy pozostaje operacją targetu sprzętowego.
### P1 — status `fast-dm`
Problem: niezaimplementowany backend był w jednym miejscu opisany jako
codzienny.
Rozstrzygnięcie: dokumenty jawnie mówią:
- teraz: `fast-rsp`;
- docelowo: `fast-dm`;
- okresowo: `fidelity-jtag`.
### P1 — USB RP2350
Problem: statyczne `--device` nie obsługuje hotplug i re-enumeracji BOOTSEL.
Rozstrzygnięcie: zewnętrzny debug probe jest stabilnym, domyślnym backendem.
Natywne USB targetu/picotool jest osobną capability i krótkotrwałą akcją z tego
samego obrazu; launcher ponownie rozwiązuje węzeł po każdej fazie. Build/test
offline nie wymagają probe.
### P2 — koszt testu 10 000 ticków
Problem: 10 000 × 150 000 daje 1,5 miliarda cykli RTL.
Rozstrzygnięcie: PR wykonuje co najmniej 100 ticków z produkcyjnym dzielnikiem,
a długi test schedulera 10 000 ticków działa z jawnie przyspieszonym zegarem.
Nightly/release otrzymuje oddzielny budżet testu produkcyjnego.
### P2 — pozostałe
- wymaganie `probe` przeniesiono z profilu na akcje `debug/deploy`;
- format instancji ujednolicono do `task<N>`;
- manifest otrzymał `default_target` per profil;
- `run rp2350` wymaga pasującego rekordu deploy albo jawnej zgody na istniejący
firmware;
- bramka przenośności rozróżnia wektory niezależne od ABI.
## Korekta jednej sugestii recenzenta
Claude zasugerował, że realny RP2350 może taktować `MTIME` inną częstotliwością
niż symulator. Jest to prawdziwe dla domyślnego trybu SIO, ale nie dla badanego
portu FreeRTOS. Lokalny
`FreeRTOS-Kernel/portable/ThirdParty/GCC/RP2350_RISC-V/port.c` wywołuje:
```c
riscv_timer_set_fullspeed(true);
uxTimerIncrementsForOneTick = clock_get_hz(clk_sys) / configTICK_RATE_HZ;
```
Przy 150 MHz i 1 kHz oba środowiska używają więc 150 000 zliczeń. Plan nadal
wymaga osobnych BSP i niezależnego wyliczania dzielnika, ponieważ tryb timera i
zegar mogą się zmienić.
## Wynik etapu projektu
Wszystkie findings P1 oraz zasadne P2 zostały naniesione do planu. Ten fragment
review odbył się przed implementacją i dlatego nie stanowił jeszcze dowodu
działania środowiska.
## Review implementacji
Po wdrożeniu Claude wykonał dwa pełne oraz zawężone przeglądy diffów. Wykryte
problemy i ich rozstrzygnięcia:
- logi GDB/JTAG/UART sugerowały `0.0.0.0`, mimo planowanego loopbacku — bind i
logi są teraz jednoznacznie `127.0.0.1`;
- Compose zawierał martwe zmienne MCP — Compose służy wyłącznie do lokalnego
buildu i niezarządzanej powłoki, a sesje MCP tworzy kanonicznie `stem`;
- zgodnościowy `tmux-container` omijał rootless runtime — deleguje teraz do
`stem shell`;
- `status/attach/stop/rm --instance` zależały od bieżącej domyślnej karty —
operują teraz po stabilnej nazwie instancji bez checkoutu karty;
- `attach` zakładał jedną błędną nazwę sesji — rozpoznaje sesje `stem-*`,
`rv32i-*`, `host-*` lub jedyną sesję z dedykowanego socketu;
- osierocony socket tmux kończył `attach` bez diagnostyki — zwracany jest teraz
komunikat nakazujący ponowne uruchomienie debug UI;
- rekord deployu pobierał commit z katalogu launchera — używa teraz
`git -C <repo-karty> rev-parse HEAD`.
Końcowe, zawężone review obu repozytoriów zakończyło się wynikiem
`NO_P0_P1`. Potwierdzone zostały także:
- build wszystkich trzech lokalnych profili z warstwami toolchainów w cache;
- 12/12 testów kontraktowych środowiska i 12/12 testów launchera;
- FreeRTOS Blink 1 kHz dla RP2350 RISC-V oraz ARM;
- wykonanie karty bare-metal w Hazard3;
- Termdebug i dashboard po lewej oraz źródło po prawej, w górnym pane tmuxa;
- działanie MCP dla tmuxa i Neovima w wybranym kontenerze;
- nasłuch symulatora GDB wyłącznie na loopbacku.
Poza bieżącym wdrożeniem pozostają jawnie oznaczone etapy badawcze:
`fast-dm` wykorzystujący Debug Module Hazard3 oraz BSP FreeRTOS dla symulatora
RTL. Nie są one przedstawiane jako gotowe.
+5 -2
View File
@@ -1164,7 +1164,10 @@ Przykłady:
## `tmux-container [series] [card]`
Tworzy nową sesję `tmux` i uruchamia kontener w `pane 0`.
Komenda zgodności. Tworzy nową sesję hostowego `tmux`, a w `pane 0` uruchamia
`stem shell` dla karty. Tworzenie kontenera, bind-mount `/workspace`, labels,
registry i sockety pozostają w jednym kanonicznym runtime rootless Podmana;
komenda nie wywołuje bezpośrednio Docker Compose.
Argumenty pozycyjne:
@@ -1180,7 +1183,7 @@ Przełączniki:
- `--window NAME`
Nadpisuje nazwę okna `tmux`.
- `--instance NAME`
Ustawia `RV_INSTANCE` dla wrappera `rv`.
Ustawia stabilne `STEM_INSTANCE` dla runtime `stem`.
- `--attach`
Po utworzeniu sesji robi `tmux attach`.
- `--dry-run`