# Fase C — Piano di applicazione (eliminazione `pwsh` dall'host Linux) > Piano operativo per [idea-3-powershell-removal.md](idea-3-powershell-removal.md). > Generato il 2026-06-07. Eseguire i passi **in ordine**: ogni passo è > autonomo, testabile e con rollback banale (i `.ps1` non vengono rimossi > finché C9 non chiude il burn-in di validazione). --- ## Summary — checklist master Stato passi (spunta man mano): - [x] **C1** — Scaffolding gruppi CLI `bench` / `smoke` / `validate` / `creds` + test skeleton - [x] **C2** — `bench run`: porting burn-in concorrenza (`Test-CapacityBurnIn` + `Start-BurnInTest*`) - [x] **C3** — `bench measure`: porting timing fasi (`Measure-CIBenchmark`) + retire `_Common.psm1`/`_Transport.psm1`/`_Common.Tests.ps1` - [x] **C4** — `smoke run`: porting smoke + build E2E (`Test-Smoke`, `Test-*Build-Linux`) - [x] **C5** — `validate host`: equivalente Linux di `Validate-HostState` - [x] **C6** — `creds set` (rimpiazza `Set-CIGuestCredential`) + `validate guest` (diag transport) - [x] **C7** — Lint CI: risolvere dipendenza `pwsh` di `lint.yml` (decisione §4 dell'analisi) - [x] **C8** — `setup-host-linux.sh`: `pwsh` non più installato di default (`--with-pwsh` opt-in) - [x] **C8.5** — (trovato 2026-07-23, non presente nell'analisi originale §2.1) `.gitea/actions/local-ci-build/action.yml` usava `shell: pwsh` in 3 step reali (introdotto 2026-05-21, prima della re-analisi C del 2026-06-07 — per questo mancava dalla tabella dipendenze). Era un blocco vero: ogni job CI, non solo il tooling manuale, richiedeva `pwsh` sull'host Linux. Riscritti in `shell: bash` (rami `$IsLinux`/`F:\CI\...` morti dalla migrazione Fase B, rimossi). Vedi commit di questa modifica. - [x] **C9** — Docs ✅ + cutover ✅ + `pwsh` **lasciato installato per scelta operativa** (non disinstallato — vedi sotto) **Definition of done globale**: tutti i criteri del §7 dell'analisi verificati; `pwsh` **rimovibile** dall'host Linux senza impatti operativi (verificato — nessun job CI né timer systemd lo richiede più); la rimozione effettiva resta una scelta operativa, non eseguita per decisione esplicita; pytest verde ≥90%. --- ## Convenzioni trasversali (valgono per ogni passo) - Codice nuovo solo in `src/ci_orchestrator/`. `click.echo`, no `print()`, no `subprocess(..., shell=True)`, type hints completi, `mypy --strict`. - Importare **solo** `backends.load_backend` + transport esistenti (`transport/ssh.py`, `transport/winrm.py`). Mai `WorkstationVmrunBackend`. - Ogni nuovo comando ha test in `tests/python/test_commands_.py` con backend e transport mockati (no VM reali in unit test). - Gate coverage 90% per ogni PR. `ruff` + `mypy --strict` verdi. - Un passo = una PR. Non toccare il branch WinRM e SSH nello stesso commit (gotcha CLAUDE.md): se un comando ha entrambi i rami, separarli o isolare dietro `if guest_os == "linux"`. --- ## C1 — Scaffolding **Obiettivo**: registrare i gruppi vuoti e i file, senza logica reale, per fissare l'interfaccia CLI e i test. - [x] Creare `commands/bench.py` con `@click.group("bench")` e i comandi stub `run` / `measure` (firma completa, corpo `raise NotImplementedError` dietro `--dry-run` per i test). - [x] Creare `commands/smoke.py` (`@click.group("smoke")` → `run`). - [x] Creare `commands/validate.py` (`@click.group("validate")` → `host`, `guest`). - [x] Creare `commands/creds.py` (`@click.group("creds")` → `set`). - [x] Registrarli in `__main__.py` (`cli.add_command(...)`). - [x] `tests/python/test_commands_bench.py` ecc.: verificare `--help` e registrazione comandi (smoke test CLI). - **Done**: `python -m ci_orchestrator bench --help` ecc. funzionano; pytest verde. ## C2 — `bench run` (burn-in concorrenza) **Sostituisce**: `Test-CapacityBurnIn.ps1`, `Start-BurnInTest.ps1`, `Start-BurnInTest-Linux.ps1`. - [x] Implementare il lancio di N job concorrenti **in-process** (`concurrent.futures` su `commands/job.py`) o come N subprocess del CLI Python — **niente `Start-Job`/`pwsh`/`$IsWindows`** (chiude il bug shim f22b142). - [x] Per ogni round, asserire: tutti i job exit 0; nessuna clone-dir orfana in `CI_BUILD_VMS`; nessun `vm-start.lock` stale (>5 min). - [x] Report JSON in `CI_ARTIFACTS/bench/` + tabella riepilogo a video. - [x] Default lab: `--concurrency 4 --rounds 10`, build-command no-op (`ping`/`sleep ~30s`) come negli wrapper. - [x] Test: round con job mockati (PASS/FAIL), rilevamento orfani e lock. - **Done**: output equivalente a `Test-CapacityBurnIn.ps1` su Win e Linux. ## C3 — `bench measure` (timing fasi) + retire moduli PS **Sostituisce**: `Measure-CIBenchmark.ps1` (+ `_Common.psm1`, `_Transport.psm1`, `tests/_Common.Tests.ps1`). - [x] Misurare fasi clone / start / IP acquire / transport ready / destroy via `load_backend` + transport, su 1+ VM effimere. - [x] Append a `benchmark.jsonl` (stesso formato, per continuità trend). - [x] Test con backend/transport mockati che simulano i tempi di fase. - [x] **Rimuovere** `scripts/_Common.psm1`, `scripts/_Transport.psm1`, `tests/_Common.Tests.ps1` (nessun altro consumatore — verificato). - [x] Aggiornare `lint.yml` se elencava esplicitamente quei path. - **Done**: `bench measure` riproduce le metriche; moduli PS rimossi; pytest verde. ## C4 — `smoke run` **Sostituisce**: `Test-Smoke.ps1`, `Test-Ns7zipBuild-Linux.ps1`, `Test-NsinnounpBuild-Linux.ps1` (e i corrispettivi Win restano per fallback). - [x] `smoke run` esegue un job no-op (marker file) end-to-end e verifica: exit 0, dir artifact creata, evento `job-success` nel JSONL. - [x] `--guest-os windows|linux`; preset build-command per le build reali ns7zip / nsinnounp (opzionali, dietro flag o `--build-command`). - [x] Test: pipeline mockata, asserzioni su artifact dir + evento. - **Done**: `smoke run --guest-os linux` e `--guest-os windows` passano. ## C5 — `validate host` **Nuovo** equivalente Linux (non porting 1:1 di `Validate-HostState.ps1`). - [x] Verificare: `vmrun` presente/eseguibile; snapshot template presenti (`BaseClean` / `BaseClean-Linux`); keyring file-based leggibile (`keyring.get_credential`); permessi `/var/lib/ci/...`; unit `act-runner` / `ci-*` attive (`systemctl is-active`). - [x] Exit 0 = tutto ok; non-zero + report per check fallito. - [x] Test: ogni check mockato (pass/fail) → exit code corretto. - **Done**: `validate host` esce 0 su host configurato. ## C6 — `creds set` + `validate guest` **Sostituisce**: `Set-CIGuestCredential.ps1` (host Linux) e `Test-CIGuestWinRM.ps1` (parte diagnostica host-side). - [x] `creds set --target ... --user ... --password-stdin`: scrive nel backend `keyring` letto da `ci_orchestrator` (`keyrings.alt.file.PlaintextKeyring`). Password mai su command line. - [x] `validate guest --host IP [--winrm|--ssh]`: tenta connessione + comando banale, riporta causa esplicita su fallimento (la sonda `is_ready` ingoia gli errori — vedi gotcha). - [x] Test: scrittura/lettura credenziale (keyring mockato); diag con transport mockato (connect ok / auth fail / connect fail). - **Done**: credenziali settabili e transport diagnosticabile senza `pwsh`. ## C7 — Lint CI (decisione `pwsh`) **Risolve dipendenza #5**. Applicare l'opzione scelta nel §4 dell'analisi. - [x] Decidere: (1) spostare job `pssa` su runner Windows / on-demand [consigliata]; (2) isolare `pwsh` nel solo runner di lint; (3) rimuovere PSSA da CI. - [x] Aggiornare `.gitea/workflows/lint.yml` di conseguenza. - [x] Verificare che nessun job che gira su `runs-on: linux-build` invochi più `shell: pwsh`. - **Done**: pipeline lint verde senza `pwsh` su runner Linux. ## C8 — `setup-host-linux.sh` - [x] Invertire lo step 6: non installare `pwsh` di default; aggiungere flag `--with-pwsh` per opt-in (mantenere `--skip-pwsh` come alias no-op deprecato o rimuoverlo). - [x] Aggiornare l'help e gli esempi d'uso nello script. - **Done**: `setup-host-linux.sh` su host pulito non installa `pwsh`. ## C9 — Docs, cutover, decisione pwsh Parte **docs** completata in codice (2026-06-07). Parte **operativa** chiusa il 2026-07-23: cutover confermato, **pwsh resta installato per scelta dell'operatore** — non è un "pendente", è una decisione presa. - [x] `docs/RUNBOOK.md`: aggiunta tabella «Phase C — Python equivalents» + nota che i record §6–§13 restano provenance storica; op-note §13 punto 3 aggiornato. - [x] `CLAUDE.md`: nota "host Linux non usa più `pwsh`" (constraints PS 5.1 restano per script guest Windows + runner Windows). - [x] `TODO.md`: chiusa voce bug shim `$IsWindows` (§3.7, risolto-per-rimpiazzo). - [x] **Cutover**: comandi Python in uso esclusivo da ≥6 settimane (timer systemd `ci-*` nativi Python da prima di C, lavoro cargo-cache 2026-07-22 interamente via `ci job`, nessun `.ps1` invocato dal path di produzione). Soddisfa l'intento del burn-in ≥1 settimana senza un log formale dedicato — è evidenza di uso reale, non un test one-shot. - [x] **action.yml** (C8.5 sopra): ultimo blocco reale rimosso — verificato che nessun job CI (incluso il composite action condiviso) richiede più `pwsh`. - [x] Riconfermare i criteri "C done" del §7 dell'analisi sull'host reale: confermati, con l'aggiunta del blocco C8.5 non previsto dall'analisi originale. - [x] **Decisione esplicita (2026-07-23)**: NON eseguire `apt-get remove powershell`. `pwsh` resta installato sull'host indefinitamente, per scelta — non perché qualcosa lo richieda ancora. Se in futuro si vuole davvero disinstallarlo, la condizione è già verificata vera (nessun impatto operativo); a quel punto è solo eseguire il comando. - **Done (codice/docs)**: porting C1–C8 + C8.5 mergeabile, suite verde ≥90%. - **Done (operativo)**: cutover confermato; rimozione pwsh deliberatamente non eseguita (scelta, non blocco tecnico). --- ## Ordine consigliato e parallelizzabilità `C1` prima di tutto. `C2`–`C6` indipendenti tra loro (parallelizzabili in PR separate, ognuna sopra C1). `C3` deve precedere la rimozione dei moduli `.psm1`. `C7` e `C8` indipendenti dai porting. `C9` ultimo, dopo che C2–C8 sono merge-ati e il burn-in di validazione è verde. ## Rischi - **WinRM 4× concorrenza instabile** (TODO noto): può far fallire `bench run` su Windows con concorrenza alta — non è regressione del porting. Tarare `--concurrency` e annotare. - **Equivalenza output**: validare A/B (`.ps1` vs Python) finché `pwsh` è ancora installato (prerequisito §6) prima della disinstallazione in C9.