# A5 — Closeout Fase A5 di `plans/implementation-plan-A-B.md`: polish, regression test per gli errori frequenti `AGENTS.md` #9-#12, documentazione Python allineata, gate coverage alzato a 80%. Branch: `feature/python-rewrite-phase-a`. ## Stato attuale - [x] `tests/python/test_agents_errors.py` aggiunto: 12 test dedicati che coprono regressioni per `AGENTS.md` "Errori frequenti da evitare" #9, #10, #11, #12. - **#9** (snapshot template con VM accesa): `clone_linked` propaga "should not be powered on" come `BackendOperationFailed` (subclass di `BackendError`) con `operation == "clone"` e `output` che contiene il messaggio originale di `vmrun`. - **#10** (`vmrun getGuestIPAddress` lento): `is_running` usa solo `vmrun list`. Tre asserzioni — happy path, VM non listata, errore di `vmrun list` — verificano che `getGuestIPAddress` non venga MAI chiamato come fallback. - **#11** (machine-id duplicato → DHCP collision): la safety net orchestrator-side è che `vm new` non riusi mai un clone-name (timestamp suffix); il test invoca `vm new` due volte con lo stesso `--job-id` e verifica che `clone_linked` riceva due `name`/`destination` distinti. Il fix template-side (`truncate -s 0 /etc/machine-id`) resta documentato in `template/Prepare-LinuxBuild2404.ps1`. - **#12** (`& nativecmd 2>$null` non sopprime stderr in PS 5.1): `SshTransport` usa `paramiko.AutoAddPolicy` di default con `known_hosts=None`, non chiama mai `subprocess.run`/`Popen` (incluso `ssh-keygen`/`ssh.exe`/`scp.exe`), e wrappa errori paramiko (import mancante, `exec_command` exception, SFTP `put`/`get`) come `TransportConnectError` invece di propagare eccezioni nude. - [x] `AGENTS.md` aggiornato: nuova sezione "Python development" subito dopo "PowerShell 5.1 — Vincoli critici", con tabella parametri, convenzioni (type hints, no `Any`/globali/`print`/`shell=True`), mappatura completa PS → Python sub-command, elenco PS che restano e gate coverage 80%. - [x] `docs/ARCHITECTURE.md` esteso con sezione "Python orchestrator (Phase A)": layout package, `VmBackend` Protocol, factory `load_backend(config)`, ricetta Phase C per backend ESXi (`backends/esxi.py` + selettore `[backend]` in `config.toml`), transport selection, gate CI. - [x] `README.md` ampliato: setup venv produzione + dev, esempi CLI per tutti i 10 sub-comandi (`wait-ready`, `vm new/remove/cleanup`, `build run`, `artifacts collect`, `monitor disk/runner`, `report job`, `job`), comandi validazione locale con gate 80%, link a `test_agents_errors.py` e `pyproject.toml`. - [x] Suite completa: ruff clean, mypy --strict clean, **116 pytest PASS**, coverage totale **80.10%** (gate 80% raggiunto). - [ ] Burn-in 4 job concorrenti × 10 round (versione Python di `Test-CapacityBurnIn`) — **richiede VM reali, lasciato a carico utente**. - [ ] Workflow `build-ns7zip.yml` matrix (Win + Linux) end-to-end PASS — eredita pendenza da A4 (richiede runner registrato). ## Definizione di "fatto" A5 | Criterio | Stato | | ----------------------------------------------------- | -------------------------------- | | Errori #9–#12 coperti da test pytest | ✅ | | `AGENTS.md` ha sezione "Python development" | ✅ | | `docs/ARCHITECTURE.md` aggiornato (layout + hook C) | ✅ | | `README.md` aggiornato (setup + esempi CLI) | ✅ | | Coverage globale ≥80% (gate alzato in pyproject) | ✅ 80.10% | | Shim PS non referenziati rimossi | ⏭️ nessuno candidato (vedi nota) | | Burn-in 4×10 PASS | ⏳ utente — richiede VM reali | **Nota shim PS**: tutti i 10 shim (`Wait-VMReady.ps1`, `New-BuildVM.ps1`, `Remove-BuildVM.ps1`, `Cleanup-OrphanedBuildVMs.ps1`, `Invoke-RemoteBuild.ps1`, `Get-BuildArtifacts.ps1`, `Watch-DiskSpace.ps1`, `Watch-RunnerHealth.ps1`, `Get-CIJobSummary.ps1`, `Invoke-CIJob.ps1`) sono ancora referenziati: `Register-CIScheduledTasks.ps1` registra scheduled task che invocano `Watch-DiskSpace.ps1`, `Watch-RunnerHealth.ps1`, `Cleanup-OrphanedBuildVMs.ps1`; `README.md` documenta `Invoke-CIJob.ps1` per uso manuale; `Test-*.ps1` di smoke chiamano gli shim. Nessuno shim è candidato a rimozione in A5 senza prima migrare anche `Register-CIScheduledTasks.ps1` (Fase B5 lo sostituirà con `*.service` + `*.timer` su systemd). ## Cosa resta a carico utente 1. Eseguire un burn-in concorrente (4 job × 10 round) usando il workflow `build-ns7zip.yml` matrix o `scripts/Test-CapacityBurnIn.ps1` (che ora delega via shim alla CLI Python `job`) e verificare: - PASS senza VM orfane (`vm cleanup` non rimuove nulla a fine round). - Wall-clock entro ±20% del baseline pre-Python (`Measure-CIBenchmark.ps1`). 2. Validare `build-ns7zip.yml` matrix (Win + Linux) end-to-end PASS sul runner reale — pendenza ereditata da A4. 3. Validare `self-test.yml` e `lint.yml` post-switch. ## Riferimenti commit - `test(a5): add pytest regression tests for AGENTS.md errors #9-#12` — `tests/python/test_agents_errors.py` - `docs(a5): document Python orchestrator in AGENTS.md, ARCHITECTURE.md, README.md` — `AGENTS.md`, `docs/ARCHITECTURE.md`, `README.md` - `docs(a5): mark A5 as code-complete; add A5-closeout.md` — `plans/implementation-plan-A-B.md`, `plans/A5-closeout.md`