Files
local-ci-cd-system/plans/implementation-plan-A-B.md
T
Simone 0f99549b70 docs: implementation-plan-A-B — Phase A closed, B kept as forward plan
Medium update: exec-summary status (A COMPLETATA, B ready), master
checklist A items [x], Phase A gating satisfied, §1 COMPLETATA banner
with real validation deviations (LocalSystem venv/keyring, github-free
artifacts, transport, shim common-params, deferred §8.1/§8.2), §7
A-side definition-of-done [x]. Fase B (§2/§3/§4) + risk matrix +
cronoprogramma left intact as the Phase B design reference alongside
PhaseB-user-checklist.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 23:29:19 +02:00

55 KiB
Raw Blame History

Piano implementativo unificato — Fase A (Python) + Fase B (host Linux)

Summary esecutivo

Obiettivo combinato: riscrivere l'orchestratore CI/CD in Python 3.11+ cross-platform (Fase A) e poi spostare l'host da Windows 11 a Linux Mint mantenendo VMware Workstation Pro come hypervisor (Fase B). Il risultato è un unico stack Python in produzione su Linux, con i template VM, i workflow Gitea Actions e act_runner invariati nella sostanza.

Sintesi fasi:

  • Fase A — Rewrite Python dell'orchestratore (host Windows attuale)
    • Output verificabile: python -m ci_orchestrator job ... sostituisce Invoke-CIJob.ps1; pytest ≥70% coverage; workflow build-ns7zip.yml PASS.
    • Stato: COMPLETATA — validata end-to-end (vedi plans/PhaseA-user-checklist.md Passi 1-9), merge feature → main 2aa12bb, tag v2.0.0-phaseA. Dettaglio storico in §1.
  • Fase B — Migrazione host a Linux Mint + Workstation Pro Linux
    • Output verificabile: act_runner come act-runner.service systemd; storage /var/lib/ci/; burn-in 4 job concorrenti PASS.
    • Stato: pronta (gating "A done" soddisfatto). Esecuzione via plans/PhaseB-user-checklist.md; design/rischi/cronoprogramma in §2/§3/§5/§6 di questo piano.
  • Fase C — (Hook only, non implementata) backend ESXi via pyVmomi
    • Output verificabile: Protocol VmBackend rispettato; [backend] selector in config.toml.
    • Stato: non in scope.

Decisioni architetturali già prese e non rinegoziabili:

  • Linguaggio: Python 3.11+, packaging pyproject.toml, CLI via click
  • Transport WinRM: pypsrp (no dipendenza da New-PSSession)
  • Transport SSH: paramiko (no dipendenza da ssh.exe / scp.exe esterni)
  • Credential store: keyring (Credential Manager su Win, Secret Service su Linux)
  • Hypervisor host Linux: VMware Workstation Pro Linux (binario /usr/bin/vmrun)
  • Astrazione hypervisor: Protocol VmBackend con prima implementazione WorkstationVmrunBackend
  • Lint dual-stack: ruff + mypy --strict per Python, PSScriptAnalyzer per i PS legacy residui

Vincoli che NON cambiano: act_runner v1.0.2+ come consumer agnostico di shell, Gitea http://10.10.20.11:3100 / https://gitea.emulab.it, template VM (WinBuild2025, WinBuild2022, LinuxBuild2404) e relativi snapshot BaseClean / BaseClean-Linux, workflow YAML in gitea/workflows/ (cambia solo la shell: e il comando invocato in Fase A4; cambiano solo path/env vars in Fase B).

Checklist riassuntiva master

  • [A1] Creare pyproject.toml + package src/ci_orchestrator/ + venv F:\CI\python\venv\
  • [A1] Implementare config.py (env vars + config.toml) con default Windows e hook path Linux
  • [A1] Implementare backends/protocol.py con Protocol VmBackend (firma neutra, no vmrun_* nei nomi pubblici)
  • [A1] Implementare backends/workstation.py (WorkstationVmrunBackend) usando subprocess + shutil.which('vmrun')
  • [A1] Implementare transport/winrm.py con wrapper pypsrp.client.Client
  • [A1] Implementare transport/ssh.py con wrapper paramiko.SSHClient / SFTPClient
  • [A1] Implementare credentials.py con Protocol CredentialStore + KeyringCredentialStore
  • [A1] PoC wait-ready end-to-end via pypsrp contro un guest Windows reale (smoke self-test PASS)
  • [A1] Test pytest unitari per vmrun.py, winrm.py, ssh.py, credentials.py con mock
  • [A1] Aggiungere job pytest a gitea/workflows/lint.yml
  • [A2] Portare Wait-VMReady.ps1python -m ci_orchestrator wait-ready
  • [A2] Portare Remove-BuildVM.ps1vm remove
  • [A2] Portare Cleanup-OrphanedBuildVMs.ps1vm cleanup
  • [A2] Portare Watch-DiskSpace.ps1monitor disk
  • [A2] Portare Watch-RunnerHealth.ps1monitor runner
  • [A2] Portare Get-CIJobSummary.ps1report job
  • [A2] Sostituire ognuno dei .ps1 portati con shim a 3 righe verso la CLI Python
  • [A3] Portare New-BuildVM.ps1vm new
  • [A3] Portare Invoke-RemoteBuild.ps1build run
  • [A3] Portare Get-BuildArtifacts.ps1artifacts collect
  • [A3] Convertire test Pester New-BuildVM.Tests.ps1, Wait-VMReady.Tests.ps1, Remove-BuildVM.Tests.ps1 in pytest
  • [A4] Portare Invoke-CIJob.ps1python -m ci_orchestrator job
  • [A4] Aggiornare gitea/actions/local-ci-build/action.yml per invocare la CLI Python direttamente
  • [A4] Forzare PYTHONIOENCODING=utf-8 in runner/config.yaml
  • [A4] Eseguire workflow build-ns7zip.yml (matrix Win+Linux) end-to-end PASS (+ job release validato con tag throwaway)
  • [A5] Convertire errori frequenti AGENTS.md #9, #10, #11, #12 in test pytest dedicati
  • [A5] Aggiornare AGENTS.md con sezione "Python development" (venv, ruff, mypy, pytest)
  • [A5] Aggiornare docs/ARCHITECTURE.md con nuovo layout package
  • [A5] Aggiornare README.md con setup Python
  • [A5] Eseguire burn-in 4 job concorrenti PASS (Start-BurnInTest: 3 round × 4 = 12/12, 0 orfani)
  • [B1] Installare Linux Mint LTS + aggiornamenti su hardware target
  • [B1] Installare VMware Workstation Pro Linux (bundle ufficiale Broadcom) e validare vmrun su VM di test
  • [B1] Configurare vmnet8 NAT range 192.168.79.0/24 (allineato all'host Windows)
  • [B1] Creare utente ci-runner (uid dedicato) e storage /var/lib/ci/{build-vms,artifacts,templates,keys} con ownership ci-runner:ci-runner mode 750
  • [B1] Installare Python 3.11+ e creare venv in /opt/ci/venv/ con pip install -e . del package portato in Fase A
  • [B1] Validare python -m ci_orchestrator --help da utente ci-runner
  • [B2] Spegnere fully powered-off i template VM sull'host Windows
  • [B2] Trasferire F:\CI\Templates\/var/lib/ci/templates/ con rsync (preservare snapshot BaseClean / BaseClean-Linux)
  • [B2] Registrare i .vmx su Workstation Linux e validare snapshot via vmrun listSnapshots
  • [B2] Smoke test manuale: vm new + wait-ready + vm remove da host Linux
  • [B3] Copiare F:\CI\keys\ci_linux*/etc/ci/keys/ con perms 600 owner ci-runner
  • [B3] Re-store credenziali BuildVMGuest e GiteaPAT con secret-tool nel keyring Linux
  • [B3] PoC accesso headless al keyring sotto systemd (file vault age/sops come fallback)
  • [B4] Scaricare act_runner Linux ≥ v1.0.2 e registrarlo verso Gitea con label windows-build:host e linux-build:host
  • [B4] Creare unit /etc/systemd/system/act-runner.service con User=ci-runner, env CI_*, PYTHONIOENCODING=utf-8
  • [B4] systemctl enable --now act-runner.service e validare via journalctl -u act-runner -f
  • [B5] Convertire Register-CIScheduledTasks.ps1 in coppie *.service + *.timer (cleanup-orphaned-vms, retention-policy, watch-disk-space, watch-runner-health, backup-template)
  • [B5] Abilitare tutti i timer con systemctl enable --now e validare systemctl list-timers
  • [B6] Stop act_runner sull'host Windows e verifica coda Gitea vuota
  • [B6] Rsync incrementale finale F:\CI\Artifacts\/var/lib/ci/artifacts/
  • [B6] Trigger smoke workflow + build-ns7zip.yml matrix da host Linux PASS
  • [B7] Eseguire burn-in 4 job concorrenti × 10 round su template Windows e Linux con tempi entro ±20% baseline
  • [B8] Backup finale di F:\CI\ su archivio
  • [B8] Lasciare host Windows spento ma reinstallato per ≥1 mese come rollback
  • [X1] Aggiornare lint.yml per girare ruff + mypy + pytest oltre a PSSA
  • [X2] Aggiungere observability: log strutturato logging + journald handler in Fase B
  • [X3] Documentare gestione credenziali headless (DPAPI machine scope su Win, vault age su Linux)
  • [X4] Aggiornare docs/RUNBOOK.md, docs/HOST-SETUP.md, docs/CI-FLOW.md end-of-Fase-B

0. Prerequisiti e gating

Da soddisfare prima di iniziare A1:

Prerequisiti Fase A soddisfatti (Fase A completata). Per BuildVMGuest emerso in validazione: deve stare nel vault LocalSystem (act_runner service), username host-qualificato — vedi scripts/Set-CIGuestCredential.ps1.

  • Repo local-ci-cd-system clonato e workflow lint.yml + self-test.yml verdi sull'host Windows attuale
  • Backup integro di F:\CI\Templates\ (snapshot BaseClean validato con vmrun listSnapshots)
  • Python 3.11+ installato sull'host Windows (venv produzione F:\CI\python\venv, install non-editable)
  • Accesso amministrativo a Gitea per registrare/de-registrare runner durante A4 e B4
  • Credenziali BuildVMGuest e GiteaPAT nel vault SYSTEM (LocalSystem), username host-qualificato
  • AGENTS.md letto integralmente da chi esegue il piano (errori #1#12 sono test case obbligatori)
  • Hardware target Linux Mint disponibile fisicamente (può restare spento fino a B1)

1. Fase A — Rewrite Python (host Windows attuale)

FASE A COMPLETATA E VALIDATA END-TO-END. Merge feature/python-rewrite-and-linux-migration → main (2aa12bb), tag v2.0.0-phaseA. Validazione: plans/PhaseA-user-checklist.md Passi 1-9 (smoke wait-ready/job Win+Linux, self-test entrambi i transport, build-ns7zip.yml matrix + job release, burn-in 12/12, benchmark). Le sottosezioni A1-A5 sotto sono record storico del piano; lo stato reale è negli [x] della checklist master e nel checklist utente.

Deviazioni/scoperte rilevanti in validazione (non previste dal piano originale, ora gestite):

  • venv produzione = install non-editable (act_runner=LocalSystem; -e punta a path non risolti → No module named ci_orchestrator). lint.yml non deve installare nella venv condivisa (venv effimera).
  • Credenziali (BuildVMGuest, GiteaPAT) nel vault SYSTEM non utente; username WinRM host-qualificato; transport WinRM forza auth=ntlm. Helper: scripts/Set-CIGuestCredential.ps1, scripts/Test-CIGuestWinRM.ps1.
  • Gitea: DEFAULT_ACTIONS_URL=github + uses: URL completo; repo action pubblico; upload/download-artifact@v4@v3 (GHES); artifact github-free (handoff via filesystem host).
  • Transport sorgente: use-git-clone/submodules default ON; variante host-side clone (--host-clone) per SSH alias host-only.
  • Shim PS: i forwarder ciechi NON inoltrano i common param PS.
  • Deferred (non bloccanti, TODO.md): §8.1 mirror actions/checkout su Gitea, §8.2 streaming live output build Windows.

A1 — Bootstrap progetto + moduli core

Obiettivo: creare scheletro package Python con VmBackend, transport WinRM/SSH e credential store funzionanti contro l'ambiente reale via PoC wait-ready.

Input / dipendenze: prerequisiti §0; scripts/_Common.psm1 e scripts/_Transport.psm1 come riferimento di comportamento atteso; AGENTS.md errori #9#12 da preservare semanticamente.

Attività:

  • Creare pyproject.toml (build backend setuptools o hatchling) con dipendenze pypsrp, paramiko, keyring, click, tomli (se Python <3.11), rich opzionale per logging
  • Creare layout src/ci_orchestrator/ con __init__.py, __main__.py (entry point click)
  • Creare venv in F:\CI\python\venv\ e installare il package in editable (pip install -e .[dev])
  • Implementare config.py: caricamento env vars (CI_ROOT, CI_TEMPLATES, CI_BUILD_VMS, CI_ARTIFACTS, CI_KEYS) con merge da config.toml opzionale; default OS-aware (Windows → F:\CI\..., Linux → /var/lib/ci/...)
  • Implementare backends/protocol.py con Protocol VmBackend (metodi clone_linked, start, stop, delete, get_ip, list_snapshots) e dataclass VmHandle (path/identificatore opaco)
  • Implementare backends/workstation.py: WorkstationVmrunBackend che usa subprocess.run([vmrun, '-T', 'ws', op, *args], check=False, capture_output=True, text=True, encoding='utf-8') con check esplicito su returncode
  • Implementare transport/winrm.py: wrapper su pypsrp.client.Client(host, username, password, ssl=True, cert_validation=False) con metodi run, copy, fetch
  • Implementare transport/ssh.py: wrapper su paramiko.SSHClient con set_missing_host_key_policy(AutoAddPolicy), known_hosts configurabile (default None per non interferire con clone ephemeri — vedi AGENTS.md errore #12)
  • Implementare credentials.py: Protocol CredentialStore + KeyringCredentialStore che usa keyring.get_credential(target, None) e ritorna oggetto Credential(username, password)
  • PoC end-to-end: comando python -m ci_orchestrator wait-ready --vmx <path> --timeout 120 su un clone reale del template WinBuild2025 (validare WinRM HTTPS self-signed)
  • Setup pytest, pytest-mock, ruff, mypy come dev dependencies; configurare pyproject.toml con [tool.ruff] e [tool.mypy] strict
  • Test pytest unitari: test_vmrun.py (mock subprocess, casi vmrun list con e senza VMX target — copre errore #10), test_winrm.py (mock pypsrp), test_ssh.py (mock paramiko, copre errore #12), test_credentials.py (mock keyring)
  • Aggiungere job python a gitea/workflows/lint.yml: setup venv → ruff check src/ tests/mypy --strict src/pytest --cov=ci_orchestrator --cov-fail-under=70

Hook futuri Fase C: il Protocol VmBackend deve usare nomi neutri (clone_linked, non vmrun_clone), accettare template/snapshot/name come stringhe opache (non path Windows), separare clone_linked da start (ESXi richiede PowerOn come task distinto). Niente assunzioni "backend == stesso host del control plane" — WorkstationVmrunBackend deve esporre la stessa firma anche se di fatto è locale.

Test / validazione:

  • pytest -q tests/ PASS
  • pytest --cov=ci_orchestrator --cov-fail-under=70 PASS
  • ruff check src/ tests/ 0 errori
  • mypy --strict src/ 0 errori
  • PoC wait-ready ritorna exit 0 entro 120s contro clone reale di WinBuild2025
  • gitea/workflows/lint.yml PASS in CI

Rollback: rimuovere cartella src/, tests/, pyproject.toml, venv F:\CI\python\venv\; ripristinare lint.yml precedente da git (git checkout HEAD~1 -- gitea/workflows/lint.yml). Nessun PS legacy modificato in A1.

Definizione di fatto step A1:

  • PoC wait-ready PASS contro VM reale
  • Coverage pytest ≥70% sui moduli core
  • lint.yml aggiornato e verde
  • Protocol VmBackend reviewato e congelato
  • Documentato setup venv in README.md (sezione minima)

A2 — Script "foglia" (no state condiviso)

Obiettivo: portare in Python tutti gli script PS che non condividono stato con l'orchestratore, sostituendoli con shim minimi.

Input / dipendenze: A1 done. File: Wait-VMReady.ps1, Remove-BuildVM.ps1, Cleanup-OrphanedBuildVMs.ps1, Watch-DiskSpace.ps1, Watch-RunnerHealth.ps1, Get-CIJobSummary.ps1.

Attività:

  • Implementare commands/wait.py con sottocomando wait-ready (parametri --vmx, --timeout, --guest-os)
  • Implementare commands/vm.py con sottocomando vm remove (parametri --vmx, --force)
  • Implementare vm cleanup (scan CI_BUILD_VMS, riconoscimento clone orfani per pattern naming + età)
  • Implementare commands/monitor.py con monitor disk (soglie configurabili, output JSON o human)
  • Implementare monitor runner (controllo processo act_runner attivo + ultimo heartbeat)
  • Implementare commands/report.py con report job (read-only su artifact dir + log job)
  • Sostituire scripts/Wait-VMReady.ps1 con shim PS 5.1 di 3 righe: & 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator wait-ready @args; exit $LASTEXITCODE
  • Idem per Remove-BuildVM.ps1, Cleanup-OrphanedBuildVMs.ps1, Watch-DiskSpace.ps1, Watch-RunnerHealth.ps1, Get-CIJobSummary.ps1
  • Aggiungere test pytest per ogni comando (mock backend + mock filesystem via tmp_path)
  • Convertire tests/Wait-VMReady.Tests.ps1 in tests/test_commands_wait.py (preservando i casi negativi)
  • Convertire tests/Remove-BuildVM.Tests.ps1 in tests/test_commands_vm_remove.py
  • Verificare che gli scheduled task esistenti (Register-CIScheduledTasks.ps1) continuino a funzionare invocando gli shim PS

Hook futuri Fase C: vm cleanup non deve assumere scan locale del filesystem — la firma deve accettare un VmBackend come dipendenza, così che in Fase C la stessa logica interroghi l'API ESXi (folder-scoped list).

Test / validazione:

  • pytest tests/test_commands_*.py PASS
  • Coverage globale rimane ≥70%
  • Trigger manuale di Watch-DiskSpace via shim PS produce stesso output semantico della versione PS originale (json comparabile)
  • Smoke: clone manuale di una VM → wait-readyvm remove end-to-end

Rollback: git checkout HEAD~1 -- scripts/Wait-VMReady.ps1 ... (ripristino .ps1 originali); rimozione commands/*.py portati; scheduled tasks restano collegati ai .ps1 ripristinati.

Definizione di fatto step A2:

  • Tutti gli script "foglia" hanno shim PS che chiama Python
  • Test pytest sostituiscono i Pester corrispondenti
  • Scheduled task continuano a funzionare via shim
  • Nessuna regressione su self-test.yml

A3 — Pipeline di build

Obiettivo: portare la pipeline core (clone → build → collect) in Python preservando il comportamento dei .ps1 esistenti.

Input / dipendenze: A2 done. File: New-BuildVM.ps1, Invoke-RemoteBuild.ps1, Get-BuildArtifacts.ps1.

Attività:

  • Implementare vm new in commands/vm.py (parametri --template, --snapshot, --name, --guest-os)
  • Integrare WorkstationVmrunBackend.clone_linked + start + attesa IP via get_ip
  • Implementare commands/build.py con build run (parametri --vmx, --script, --workdir, --guest-os); usa transport.winrm per Windows, transport.ssh per Linux
  • Gestione streaming output build verso stdout (act_runner cattura stdout — preservare comportamento Write-Host)
  • Implementare commands/artifacts.py con artifacts collect (parametri --vmx, --remote-path, --local-dir)
  • Sostituire scripts/New-BuildVM.ps1, Invoke-RemoteBuild.ps1, Get-BuildArtifacts.ps1 con shim
  • Convertire tests/New-BuildVM.Tests.ps1 in tests/test_commands_vm_new.py
  • Aggiungere test pytest per build run (mock transport + capture stdout)
  • Aggiungere test pytest per artifacts collect (mock SFTP/WinRM file copy + tmp_path)
  • Validare end-to-end: clone WinBuild2025 → build script PowerShell trivial → collect artifact ZIP

Hook futuri Fase C: build run deve ricevere un VmHandle opaco, non un path VMX (ESXi non ha path locale al control plane). Adattare le CLI a accettare entrambe (--vmx per workstation, --vm-id futuro per ESXi) con dispatcher nel layer commands/.

Test / validazione:

  • pytest tests/test_commands_vm_new.py tests/test_commands_build.py tests/test_commands_artifacts.py PASS
  • Coverage globale ≥70%
  • Smoke: workflow CI manuale end-to-end usando esclusivamente gli shim
  • Test-NsinnounpBuild.ps1 continua a passare (usa shim sotto)

Rollback: ripristino .ps1 originali da git; rimozione comandi Python; nessuna modifica ai workflow YAML in A3 (solo shim).

Definizione di fatto step A3:

  • Pipeline build completa accessibile via Python CLI
  • Shim PS preservano l'API per i caller esistenti
  • Smoke build PASS contro VM reale Windows e Linux
  • Pester New-BuildVM.Tests.ps1 rimosso e sostituito

A4 — Orchestratore + switch workflow

Obiettivo: portare Invoke-CIJob.ps1 e fare switch dei workflow Gitea per chiamare la CLI Python direttamente.

Input / dipendenze: A3 done. File: Invoke-CIJob.ps1, gitea/actions/local-ci-build/action.yml, runner/config.yaml.

Attività:

  • Implementare commands/job.py con sottocomando job (entry point completo: parsing parametri job, clone, wait, build, collect, cleanup)
  • Gestione errori e cleanup garantito (try/finally con vm remove anche su failure)
  • Aggiornare gitea/actions/local-ci-build/action.yml: cambiare shell: powershellshell: cmd o invocazione diretta python -m ci_orchestrator job ...
  • Aggiungere PYTHONIOENCODING=utf-8 in runner/config.yaml env
  • Lasciare Invoke-CIJob.ps1 come shim per scheduled task / chiamate manuali esterne (rimosso in A5)
  • Test pytest end-to-end con mock backend + transport: test_commands_job.py
  • Eseguire workflow build-ns7zip.yml matrix (Win + Linux) PASS
  • Eseguire workflow self-test.yml PASS
  • Eseguire workflow lint.yml PASS

Hook futuri Fase C: la selezione del backend deve avvenire in job.py leggendo config.toml [backend].type (default workstation). Niente import diretto di WorkstationVmrunBackend in job.py — usare factory backends.load_backend(config).

Test / validazione:

  • pytest tests/test_commands_job.py PASS con coverage ≥80% sul comando job
  • Workflow build-ns7zip.yml end-to-end PASS dal nuovo entry point
  • Output catturato da act_runner leggibile (no encoding broken, no ANSI rotti)
  • Tempi job entro ±10% rispetto al baseline PS (misurati con Measure-CIBenchmark ancora in PS, lecito)

Rollback: git checkout HEAD~1 -- gitea/actions/local-ci-build/action.yml runner/config.yaml; Invoke-CIJob.ps1 originale è ancora in git history e referenziabile.

Definizione di fatto step A4:

  • action.yml chiama Python direttamente
  • Workflow matrix Win+Linux PASS
  • runner/config.yaml ha PYTHONIOENCODING=utf-8
  • Coverage job.py ≥80%
  • Misurazione tempo entro ±10% baseline

A5 — Test, documentazione, cleanup

Obiettivo: chiudere debito tecnico, rimuovere shim ridondanti, aggiornare documentazione, eseguire burn-in finale.

Input / dipendenze: A4 done.

Attività:

  • Implementare test pytest dedicati per ognuno degli errori frequenti AGENTS.md #9 (snapshot powered-off), #10 (vmrun list vs getGuestIPAddress), #11 (machine-id reset → unique clone-name guard), #12 (stderr handling → AutoAddPolicy + no shell-out)
  • Verificare con grep_search che gli shim PS abbiano ancora call site esterni (Register-CIScheduledTasks.ps1, doc, Test-*.ps1) → nessuno candidato a rimozione in A5; rimozione differita a B5 (systemd timers)
  • Lasciare Install-CIToolchain-*.ps1, Prepare-*.ps1, Deploy-*.ps1 in PowerShell (girano dentro guest o per ricostruzione template — vedi §4 di idea-1)
  • Aggiornare AGENTS.md: aggiungere sezione "Python development" (venv path, ruff, mypy strict, pytest, encoding utf-8, mappatura PS → Python)
  • Aggiornare docs/ARCHITECTURE.md con nuovo layout src/ci_orchestrator/ + Phase C extension point
  • Aggiornare README.md con setup Python e quick-start CLI per tutti i 10 sub-comandi
  • Portare Test-CapacityBurnIn.ps1 in commands/burnin.py (o lasciare in PS se ancora utile come driver esterno) — non implementato in A5; lo shim attuale già delega via job Python
  • Eseguire burn-in 4 job concorrenti × 10 round PASS sull'host Windows — richiede VM reali, a carico utente
  • Aggiornare docs/RUNBOOK.md con sezione "Operare da Python CLI" — deferito a chiusura Fase B (RUNBOOK riscritto end-to-end in B-finale)

Hook futuri Fase C: documentare in docs/ARCHITECTURE.md il contratto VmBackend come "extension point" e il selettore [backend] in config.toml come unica via per aggiungere ESXi.

Test / validazione:

  • pytest -q PASS, coverage globale ≥70%
  • ruff check + mypy --strict puliti
  • lint.yml PASS (PSSA su PS residui + ruff/mypy/pytest su Python)
  • Burn-in 4×10 PASS senza VM orfane
  • grep_search Invoke-CIJob nei workflow YAML ritorna 0 hit

Rollback: ripristino doc da git; ri-aggiungere shim rimossi se qualche caller esterno non ancora migrato emerge.

Definizione di fatto step A5:

  • Errori #9#12 coperti da test pytest (tests/python/test_agents_errors.py, 12 test)
  • Documentazione aggiornata (AGENTS.md, docs/ARCHITECTURE.md, README.md)
  • Shim non referenziati rimossi → nessuno candidato (tutti referenziati da Register-CIScheduledTasks.ps1/doc/Test-*.ps1); rimozione differita a B5 con sostituzione systemd
  • Burn-in PASS — Start-BurnInTest.ps1 3 round × 4 job = 12/12, 0 orfani
  • Fase A done: validata end-to-end su hardware reale; mergiata in main (2aa12bb), tag v2.0.0-phaseA

2. Fase B — Migrazione host Linux Mint

Parallelizzazione: B1 può iniziare durante A3/A4 (setup hardware indipendente dal codice). B2, B3 possono iniziare appena B1 è done e A5 è done. B4B6 sono strettamente sequenziali dopo "A done".

B1 — Setup host Linux Mint in parallelo

Obiettivo: preparare hardware Linux Mint con Workstation Pro Linux, storage layout e venv Python pronti, senza impatto produzione.

Input / dipendenze: hardware target disponibile. Può girare in parallelo a A3/A4. Riferimento idea-2-linux-host.md §4.

Attività:

  • Installare Linux Mint LTS sull'hardware target + aggiornamenti (apt full-upgrade)
  • Scaricare e installare VMware Workstation Pro Linux (bundle .bundle da Broadcom)
  • Validare vmrun -T ws --version ritorna versione coerente
  • Smoke test Workstation: creare VM trivia, clone, start, stop, delete via UI e via vmrun
  • Configurare vmnet8 NAT range 192.168.79.0/24 (allineato all'host Windows) editando /etc/vmware/vmnet8/dhcpd/dhcpd.conf o via vmware-netcfg
  • Creare utente di servizio ci-runner (useradd -r -m -s /bin/bash ci-runner) con uid/gid dedicati
  • Creare layout /var/lib/ci/{build-vms,artifacts,templates,keys} con ownership ci-runner:ci-runner mode 750
  • Applicare ACL POSIX: setfacl -d -m u:ci-runner:rwX /var/lib/ci/build-vms
  • Installare Python 3.11+ (apt install python3.11 python3.11-venv)
  • Creare venv /opt/ci/venv/ con python3.11 -m venv e installare il package: /opt/ci/venv/bin/pip install -e /path/to/local-ci-cd-system
  • Validare sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator --help
  • Aggiungere [paths.linux] a config.example.toml con i path POSIX

Hook futuri Fase C: lo stesso host Linux farà da control plane se si attiverà ESXi. Non installare nulla che assuma "VM girano in locale" (es. condivisioni vmware-shared-folders host-only).

Rischi specifici (da idea-2-linux-host.md §6):

  • vmrun su Linux ha differenze sottili nel parsing output → mitigazione: test del modulo vmrun.py su Linux subito in B1, confronto output con Windows
  • Permessi /var/lib/ci/build-vms/: vmrun come ci-runner deve scrivere VMDK CoW → mitigazione: ACL POSIX + test scrittura
  • vmnet8 NAT range diverso → mitigazione: allineamento range a 192.168.79.0/24

Test / validazione:

  • vmrun -T ws list ritorna lista vuota o coerente
  • python -m ci_orchestrator --help PASS da ci-runner
  • ACL POSIX verificata con getfacl /var/lib/ci/build-vms
  • pytest (subset cross-platform) PASS sul venv Linux

Rollback: il host Linux è fisicamente separato. Spegnerlo. Nessun impatto su produzione Windows.

Definizione di fatto step B1:

  • Workstation Pro Linux operativa
  • Storage layout creato con ACL
  • Python venv pronto e package installato
  • vmnet8 configurato

B2 — Trasferimento template VM

Obiettivo: copiare i template VMware dall'host Windows a Linux mantenendo snapshot integri.

Input / dipendenze: B1 done. Può girare durante A4/A5. Riferimento AGENTS.md errore #9 (snapshot solo da powered-off).

Attività:

  • Verificare assenza di *.vmem / *.vmsn di runtime in F:\CI\Templates\<name>\ (template fully powered-off)
  • Eseguire rsync -av --progress /cygdrive/f/CI/Templates/ ci-runner@<linux-host>:/var/lib/ci/templates/ (o scp -r se rsync non disponibile)
  • Verificare integrità: find /var/lib/ci/templates -name '*.vmx' -exec vmrun -T ws listSnapshots {} \;
  • Aprire i .vmx su Workstation Linux per registrarli (eventuale prompt "I moved it / I copied it" → "I copied it")
  • Validare snapshot BaseClean su WinBuild2025.vmx, WinBuild2022.vmx
  • Validare snapshot BaseClean-Linux su LinuxBuild2404.vmx
  • Smoke test: python -m ci_orchestrator vm new --template /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx --snapshot BaseClean --name smoke1 + wait-ready + vm remove

Hook futuri Fase C: i template restano in formato VMX. Per ESXi serviranno OVF (ovftool) — non fare nulla in B2, ma documentare che il dataset attuale è la base per export futuro.

Rischi specifici:

  • Performance I/O linked clone su ext4 vs NTFS → mitigazione: misurare in B7, eventualmente XFS/BTRFS
  • Snapshot corrotto in transit → mitigazione: checksum (sha256sum) prima e dopo rsync

Test / validazione:

  • vmrun listSnapshots ritorna gli snapshot attesi su tutti i template
  • Smoke vm new + wait-ready PASS con WinRM da Linux verso guest Windows
  • Smoke vm new + wait-ready PASS con SSH da Linux verso guest Linux

Rollback: i template originali su F:\CI\Templates\ sono intatti. Spegnere host Linux. Eventualmente eliminare /var/lib/ci/templates/.

Definizione di fatto step B2:

  • Tutti i template registrati su Workstation Linux
  • Snapshot integri verificati
  • Smoke vm new PASS per Win e Linux

B3 — Trasferimento credenziali e chiavi

Obiettivo: rendere disponibili sull'host Linux le credenziali guest e i token Gitea, accessibili da ci-runner headless.

Input / dipendenze: B1 done. Può girare durante A5. Riferimento idea-2-linux-host.md §6 (rischio Alta su keyring headless).

Attività:

  • Copiare F:\CI\keys\ci_linux e ci_linux.pub/etc/ci/keys/ con chmod 600 e chown ci-runner:ci-runner
  • Re-store credenziale guest Windows: sudo -u ci-runner secret-tool store --label='BuildVMGuest' service ci target BuildVMGuest
  • Re-store Gitea PAT: sudo -u ci-runner secret-tool store --label='GiteaPAT' service ci target GiteaPAT
  • Validare lettura: sudo -u ci-runner /opt/ci/venv/bin/python -c "import keyring; print(keyring.get_credential('BuildVMGuest', None))"
  • PoC headless: testare lettura keyring da contesto systemd (no sessione D-Bus utente). Se fallisce, implementare backend keyring file-based con vault age o sops come fallback documentato
  • Documentare la scelta finale in docs/HOST-SETUP.md

Hook futuri Fase C: anche ESXi userà credenziali da keyring (user vSphere). Lo stesso KeyringCredentialStore deve poter caricare EsxiHost target — niente hard-coding di nomi credenziali.

Rischi specifici:

  • secret-tool headless: act_runner systemd non ha D-Bus user session → mitigazione: backend file-based con age (decisione PoC in B3)

Test / validazione:

  • Lettura keyring PASS da utente interattivo
  • Lettura keyring PASS da contesto systemd (systemd-run --uid=ci-runner ... come simulazione)
  • Pytest test_credentials.py con backend Linux PASS

Rollback: rimuovere /etc/ci/keys/, secret-tool clear per ogni target. Credenziali su host Windows intatte.

Definizione di fatto step B3:

  • Chiavi SSH copiate con perms corretti
  • Credenziali nel keyring leggibili headless
  • Strategia documentata in docs/HOST-SETUP.md

B4 — Setup act_runner come systemd service

Obiettivo: act_runner Linux registrato verso Gitea e gestito da systemd, equivalente al servizio Windows attuale.

Input / dipendenze: B1, B2, B3 done. Sequenziale dopo "A done" (serve la CLI Python stabile per i comandi invocati dai workflow).

Attività:

  • Scaricare binario act_runner Linux ≥ v1.0.2 in /opt/ci/act_runner/
  • Generare token registrazione su Gitea per il nuovo runner
  • Registrare runner: act_runner register --no-interactive --instance <gitea-url> --token <t> --name ci-linux --labels windows-build:host,linux-build:host
  • Creare /etc/systemd/system/act-runner.service:
    • [Service] User=ci-runner, WorkingDirectory=/var/lib/ci/runner, ExecStart=/opt/ci/act_runner/act_runner daemon
    • Environment="PYTHONIOENCODING=utf-8", Environment="CI_ROOT=/var/lib/ci", Environment="CI_TEMPLATES=/var/lib/ci/templates", Environment="CI_BUILD_VMS=/var/lib/ci/build-vms", Environment="CI_ARTIFACTS=/var/lib/ci/artifacts", Environment="CI_KEYS=/etc/ci/keys"
    • Restart=on-failure, RestartSec=10
  • systemctl daemon-reload && systemctl enable --now act-runner.service
  • Validare journalctl -u act-runner -f mostra connessione a Gitea OK
  • NON avviare ancora workflow di produzione — il runner Windows è ancora primario

Hook futuri Fase C: env vars CI_* restano valide; per ESXi si aggiungeranno solo CI_BACKEND=esxi + sezione [backend.esxi] in config.toml. act-runner.service non cambia.

Rischi specifici:

  • Runner Linux in idle che intercetta job destinati a Windows → mitigazione: in B4 il runner viene registrato ma in stato paused lato Gitea, oppure label distinte temporanee fino a B6

Test / validazione:

  • systemctl status act-runneractive (running)
  • Runner visibile in Gitea admin UI come "online"
  • Job di test manuale (workflow self-test.yml) PASS sul runner Linux
  • journalctl -u act-runner --since "5min ago" senza errori critici

Rollback: systemctl disable --now act-runner, de-registrare runner da Gitea. Runner Windows resta primario.

Definizione di fatto step B4:

  • act-runner.service attivo e abilitato
  • Runner online in Gitea
  • self-test.yml PASS dal nuovo runner
  • Logging via journald validato

B5 — Conversione scheduled tasks → systemd timers

Obiettivo: tutti i task periodici del runner Windows sono replicati come coppie *.service + *.timer su Linux.

Input / dipendenze: B4 done. Riferimento Register-CIScheduledTasks.ps1 per l'inventario dei task.

Attività:

  • Inventariare i task in Register-CIScheduledTasks.ps1 (identificare cadenza e comando per ognuno)
  • Per cleanup-orphaned-vms: creare ci-cleanup-orphaned-vms.service (oneshot, ExecStart=/opt/ci/venv/bin/python -m ci_orchestrator vm cleanup) + .timer (OnCalendar=hourly)
  • Per retention-policy: creare ci-retention-policy.service + .timer (OnCalendar=daily)
  • Per watch-disk-space: ci-watch-disk-space.service + .timer (OnCalendar=*:0/15)
  • Per watch-runner-health: ci-watch-runner-health.service + .timer (OnCalendar=*:0/5)
  • Per backup-template: ci-backup-template.service + .timer (OnCalendar=weekly)
  • systemctl daemon-reload e systemctl enable --now <ognuno>.timer
  • Validare systemctl list-timers mostra tutti i timer schedulati
  • Documentare il mapping in docs/HOST-SETUP.md

Hook futuri Fase C: vm cleanup con backend ESXi userà la stessa unit — il selettore backend è in config.toml, non nel comando.

Rischi specifici:

  • Cadenza non perfettamente equivalente tra Task Scheduler e systemd timers → mitigazione: usare OnCalendar esplicito + Persistent=true per non perdere esecuzioni durante reboot

Test / validazione:

  • systemctl list-timers --all mostra tutti i timer attesi
  • Trigger manuale systemctl start ci-cleanup-orphaned-vms.service esegue senza errori
  • journalctl -u ci-* mostra esecuzioni riuscite

Rollback: systemctl disable --now <name>.timer per ognuno; rimuovere file unit. Task Scheduler Windows ancora attivo.

Definizione di fatto step B5:

  • Tutti i task periodici come timer systemd attivi
  • Trigger manuale di ognuno PASS
  • Mapping documentato

B6 — Cutover

Obiettivo: transizione produzione dall'host Windows al Linux in una finestra di manutenzione concordata.

Input / dipendenze: A done, B1B5 done. Strettamente sequenziale.

Attività:

  • Annunciare finestra di manutenzione
  • Verificare nessun job in coda lato Gitea
  • Stop act_runner sull'host Windows: Stop-Service actions-runner (PS 5.1, $LASTEXITCODE controllato)
  • Disabilitare scheduled task Windows: Unregister-ScheduledTask -TaskName 'CI-*' -Confirm:$false
  • Rsync incrementale finale F:\CI\Artifacts\/var/lib/ci/artifacts/ (preserva permessi)
  • Verificare che il runner Linux sia "online" e non "paused" su Gitea
  • Trigger manuale di workflow smoke (self-test.yml) PASS
  • Trigger manuale build-ns7zip.yml matrix Win + Linux PASS
  • Monitorare journalctl -u act-runner -f per ≥30 min sotto carico reale

Hook futuri Fase C: nessuno specifico in B6. Cutover non tocca backend.

Rischi specifici:

  • Gap di tempo tra stop runner Windows e start runner Linux durante il quale i job vanno in errore → mitigazione: cutover in finestra a zero traffic + Gitea UI "paused" preventivo
  • WinRM da Linux verso guest Windows fallisce su edge case TLS → mitigazione: test in B2 + B4, fallback documentato in idea-2-linux-host.md §7 (dual-host)

Test / validazione:

  • self-test.yml PASS dal runner Linux
  • build-ns7zip.yml matrix Win+Linux PASS dal runner Linux
  • Nessun errore in journalctl -u act-runner --since "1h ago" di severità critica
  • Artifact correttamente in /var/lib/ci/artifacts/

Rollback (entro 30 min se PASS critico fallisce):

  • systemctl stop act-runner su Linux
  • Start-Service actions-runner su Windows
  • Register-ScheduledTask per riabilitare task Windows
  • Documentare incident e tornare a B1B5 per fix

Definizione di fatto step B6:

  • Runner Linux primario, runner Windows fermo
  • Workflow matrix PASS dal nuovo host
  • Artifact migrati
  • 30 min di esercizio senza incidenti

B7 — Capacity burn-in sul nuovo host

Obiettivo: validare che il nuovo host regga il carico target con tempi paragonabili.

Input / dipendenze: B6 done.

Attività:

  • Eseguire burn-in 4 job concorrenti × 10 round su template WinBuild2025
  • Eseguire burn-in 4 job concorrenti × 10 round su template LinuxBuild2404
  • Confrontare tempi medi vs baseline pre-migrazione (registrato in A5)
  • Verificare success rate 100% (no VM orfane, no failure transienti)
  • Misurare uso disco /var/lib/ci/build-vms/ durante e dopo burn-in (cleanup automatico)
  • Documentare risultati in docs/RUNBOOK.md

Hook futuri Fase C: i risultati di B7 sono il baseline contro cui confrontare un eventuale backend ESXi (criterio di "C done" in idea-3-esxi-support.md §8).

Rischi specifici:

  • Performance I/O ext4 inferiore → mitigazione: se delta >20%, valutare XFS/BTRFS
  • VM orfane non pulite → mitigazione: timer ci-cleanup-orphaned-vms deve essere già attivo (B5)

Test / validazione:

  • Burn-in PASS con success rate 100%
  • Tempo medio entro ±20% baseline Windows
  • Nessuna VM orfana al termine
  • Uso disco torna al baseline post-cleanup

Rollback: non applicabile (B7 è validazione, non modifica). Se fallisce, rollback a Windows (vedi B6 rollback).

Definizione di fatto step B7:

  • Burn-in PASS
  • Tempi documentati
  • Nessuna VM orfana

B8 — Decommissioning host Windows (opzionale)

Obiettivo: dopo ≥1 settimana di stabilità, dismettere host Windows.

Input / dipendenze: B7 done + ≥1 settimana di esercizio Linux senza incidenti.

Attività:

  • Backup finale di F:\CI\ su archivio esterno (tar + checksum)
  • Spegnimento runner Windows (Stop-Service actions-runner, runner già fermo da B6)
  • Lasciare host Windows installato ma spento per ≥1 mese come rollback
  • Documentare procedura di riaccensione in docs/RUNBOOK.md
  • Dopo 1 mese: riallocare hardware se non ci sono stati rollback

Hook futuri Fase C: nessuno.

Rischi specifici:

  • Decommissioning prematuro elimina rollback → mitigazione: gating ≥1 settimana + ≥1 mese spento prima di riallocare

Test / validazione:

  • Backup verificato (estrazione di prova in tmp)
  • Host Windows spento, hardware integro
  • Runbook aggiornato

Rollback: riaccendere host Windows, ripristinare scheduled task, re-registrare runner Windows verso Gitea.

Definizione di fatto step B8:

  • Backup F:\CI\ archiviato e validato
  • Host Windows spento ma intatto
  • Runbook rollback documentato
  • Fase B done: tutti i criteri di "fatto" §7 soddisfatti

3. Step trasversali (X)

X1 — Lint dual-stack (PSSA + ruff/mypy)

Obiettivo: pipeline di lint unica che copre Python e PowerShell residuo.

Input / dipendenze: A1 (per la parte Python). Aggiornata in A5 e B8.

Attività:

  • Aggiornare gitea/workflows/lint.yml con job python-lint (ruff + mypy + pytest) e job pwsh-lint (PSSA su scripts/, template/, tests/)
  • In B8: rimuovere o relegare a job opzionale la parte PSSA (resta utile solo per template/ Windows)
  • Configurare PSScriptAnalyzerSettings.psd1 per coprire scripts/, template/, tests/ (già presente in repo)
  • Configurare pyproject.toml con [tool.ruff], [tool.mypy], [tool.pytest.ini_options]

Hook futuri Fase C: nessuno specifico.

Test / validazione:

  • lint.yml PASS con entrambi i job
  • 0 errori PSSA, 0 errori ruff, 0 errori mypy strict

Rollback: ripristino lint.yml da git.

Definizione di fatto step X1:

  • lint.yml con job dual-stack
  • 0 warning bloccanti
  • Documentato in AGENTS.md

X2 — Observability

Obiettivo: log strutturati + integrazione journald in Fase B.

Input / dipendenze: A1 (logger setup); B4 (systemd journald).

Attività:

  • Configurare logging Python con formatter strutturato (JSON o key=value), nessun colore ANSI obbligatorio (act_runner non li gestisce sempre)
  • In B4: configurare service unit per inviare stdout/stderr a journald (default systemd) e validare journalctl -u act-runner -o json
  • Documentare query journald utili in docs/RUNBOOK.md

Hook futuri Fase C: log strutturati facilitano correlazione job ↔ backend.

Test / validazione:

  • journalctl -u act-runner -o json ritorna eventi parsabili
  • Log Python catturato correttamente da act_runner stdout

Rollback: revert formatter logging.

Definizione di fatto step X2:

  • Logging strutturato
  • journald query documentate

X3 — Gestione credenziali headless

Obiettivo: credenziali accessibili da contesto SYSTEM (Win) e systemd (Linux) senza sessione utente interattiva.

Input / dipendenze: A1 (credential store); B3 (PoC headless Linux).

Attività:

  • Documentare in docs/HOST-SETUP.md il problema noto: keyring Win sotto SYSTEM e keyring Linux sotto systemd richiedono soluzioni dedicate
  • In Fase A: documentare l'uso corrente (Credential Manager con utente di servizio + DPAPI machine scope)
  • In B3: PoC + decisione finale (Secret Service via D-Bus user@.service, oppure file vault age/sops)
  • Implementare backend keyring file-based come opzione KeyringFileBackend(vault_path, age_key_path) se serve
  • Test pytest che simulano contesto headless (mock keyring.get_credential ritorna None → fallback a file vault)

Hook futuri Fase C: stesso CredentialStore userà credenziali vSphere — mantenere API generica.

Test / validazione:

  • Lettura credenziale da contesto headless PASS in entrambi gli OS
  • Test pytest fallback PASS

Rollback: usare credenziali in file plaintext non accettabile — nessun rollback se PoC fallisce, va risolto come blocker.

Definizione di fatto step X3:

  • Strategia documentata
  • Test pytest fallback verde
  • PoC Linux PASS

X4 — Documentazione consolidata

Obiettivo: documentazione docs/ allineata allo stato finale (host Linux + Python).

Input / dipendenze: A5 e B8.

Attività:

  • Aggiornare docs/ARCHITECTURE.md: layout src/ci_orchestrator/, Protocol VmBackend, hook ESXi
  • Aggiornare docs/HOST-SETUP.md: setup Linux Mint, Workstation Pro Linux, ACL, keyring headless
  • Aggiornare docs/CI-FLOW.md: nuovo entry point Python, env vars
  • Aggiornare docs/RUNBOOK.md: comandi systemd, journalctl, troubleshooting
  • Aggiornare docs/BEST-PRACTICES.md: convenzioni Python (no path hardcoded, ruff, mypy strict)
  • Aggiornare AGENTS.md: sezione "Python development" + nota errore #12 N/A in Python
  • Aggiornare README.md: quick-start Python + Linux

Hook futuri Fase C: lasciare placeholder "Backend ESXi (futuro)" in docs/ARCHITECTURE.md e docs/HOST-SETUP.md.

Test / validazione:

  • Lettura manuale: un nuovo operatore deve poter setuppare host Linux + lanciare un job seguendo solo docs/
  • Link interni risolvono (grep_search su \[.*\]\(.*\.md\))

Rollback: revert doc da git.

Definizione di fatto step X4:

  • Docs aggiornate
  • Link interni validi
  • Quick-start testato da operatore esterno

4. Hook architetturali per Fase C

Punti di design da rispettare durante A e B per non bloccare un futuro backend ESXi senza implementarlo:

  • Protocol VmBackend in src/ci_orchestrator/backends/protocol.py: metodi clone_linked(template, snapshot, name) -> VmHandle, start(vm), stop(vm, hard=True), delete(vm), get_ip(vm, timeout) -> str, list_snapshots(template) -> list[str]. Firma neutra, nessun riferimento a vmrun, vmx, esxi nei nomi pubblici.
  • VmHandle dataclass: campo id opaco (string). Per Workstation contiene il path VMX, per ESXi conterrà il MoRef. Chi consuma VmHandle non può fare assunzioni sul contenuto.
  • Selezione backend via config: config.toml ha sezione [backend] con type = "workstation" (default) o "esxi" (futuro). Factory backends.load_backend(config) istanzia il backend corretto. Niente import diretto di WorkstationVmrunBackend nei comandi.
  • Separazione control plane / compute: il control plane (host con act_runner + Python) non deve assumere che le VM girino in locale. Le funzioni vm new / vm cleanup ricevono il backend come parametro, non leggono filesystem locale (eccetto per WorkstationVmrunBackend internamente).
  • Naming neutro nelle API pubbliche: i sottocomandi CLI sono vm new, vm remove, vm cleanup (non vmrun-clone). Le env vars sono CI_BUILD_VMS, CI_TEMPLATES (non VMRUN_*).
  • Credenziali: CredentialStore accetta target name arbitrario. Per ESXi si aggiungerà target EsxiHost senza modifiche al codice.
  • Logging strutturato: include sempre backend=<type> e vm_id=<handle.id> nei log per tracciabilità multi-backend.

Riferimento esplicito: idea-3-esxi-support.md §3 (interfaccia EsxiBackend) e §4 (mappa operazioni).

5. Cronoprogramma e dipendenze

flowchart TD
    P0[0. Prerequisiti] --> A1
    A1[A1 Bootstrap + core] --> A2[A2 Script foglia]
    A2 --> A3[A3 Pipeline build]
    A3 --> A4[A4 Orchestratore + workflow]
    A4 --> A5[A5 Test + docs + cleanup]
    A1 --> X1[X1 Lint dual-stack]
    A1 --> X2[X2 Observability]
    A1 --> X3[X3 Credenziali headless]
    A3 -.parallelo.-> B1[B1 Setup host Linux]
    A4 -.parallelo.-> B1
    B1 --> B2[B2 Trasferimento template]
    B1 --> B3[B3 Credenziali Linux]
    A5 --> B4
    B2 --> B4[B4 act_runner systemd]
    B3 --> B4
    B4 --> B5[B5 Timer systemd]
    B5 --> B6[B6 Cutover]
    B6 --> B7[B7 Burn-in Linux]
    B7 --> B8[B8 Decommissioning Win]
    A5 --> X4[X4 Docs consolidate]
    B8 --> X4
    X3 --> B3
    X1 --> A5
    X2 --> B4

6. Matrice rischi consolidata

Ogni voce: Rischio — Fase / Severità / Mitigazione / Owner action item.

  • pypsrp edge case con WinRM HTTPS self-signed
    • Fase: A1 — Severità: Media
    • Mitigazione: PoC wait-ready come primo deliverable A1 prima di committare al resto.
    • Owner: A1 attività #10.
  • keyring sotto SYSTEM (act_runner service) non vede credenziali utente
    • Fase: A1, B3 — Severità: Alta
    • Mitigazione: DPAPI machine scope su Win; PoC vault file age/sops su Linux in B3.
    • Owner: X3 attività complete.
  • Perdita di know-how errori AGENTS.md #9#12 durante refactor
    • Fase: A5 — Severità: Media
    • Mitigazione: ognuno convertito in test pytest prima di rimuovere il .ps1 corrispondente.
    • Owner: A5 attività #1.
  • Doppio mantenimento PS+Python durante migrazione
    • Fase: A2A4 — Severità: Alta
    • Mitigazione: strategia shim minimizza la finestra; lint.yml dual-stack.
    • Owner: X1 attività complete.
  • Astrazione VmBackend over-engineered se Fase C non parte mai
    • Fase: A1 — Severità: Bassa
    • Mitigazione: 1 implementazione concreta + Protocol = ~50 LOC extra; costo trascurabile.
    • Owner: accettato.
  • act_runner cattura male output Python (encoding cp1252)
    • Fase: A4 — Severità: Media
    • Mitigazione: PYTHONIOENCODING=utf-8 in runner/config.yaml.
    • Owner: A4 attività #4.
  • vmrun su Linux ha differenze parsing output list / getGuestIPAddress
    • Fase: B1 — Severità: Media
    • Mitigazione: test modulo vmrun.py su Linux subito in B1.
    • Owner: B1 attività #3.
  • Performance I/O linked clone su ext4 vs NTFS
    • Fase: B7 — Severità: Bassa
    • Mitigazione: misurare con burn-in B7; eventualmente XFS/BTRFS.
    • Owner: B7 attività #5.
  • vmnet8 NAT range diverso → IP collision LAN
    • Fase: B1 — Severità: Media
    • Mitigazione: allineare 192.168.79.0/24 in B1.
    • Owner: B1 attività #5.
  • WinRM HTTPS da Linux: TLS 1.2 cipher non supportati dal Windows guest
    • Fase: B2 — Severità: Media
    • Mitigazione: smoke test prima del cutover; stesso codice già testato in A1 da Win host.
    • Owner: B2 attività #7.
  • Permessi /var/lib/ci/build-vms/ per ci-runner
    • Fase: B1 — Severità: Media
    • Mitigazione: ACL POSIX con setfacl -d -m.
    • Owner: B1 attività #8.
  • secret-tool headless senza D-Bus session
    • Fase: B3 — Severità: Alta
    • Mitigazione: PoC + fallback file vault age.
    • Owner: B3 attività #5.
  • Errore AGENTS.md #11 (machine-id Linux) si ripropone
    • Fase: B2 — Severità: Bassa
    • Mitigazione: già documentato; fix nel template prima dello snapshot.
    • Owner: preservato.
  • Errore AGENTS.md #12 (stderr nativa + 'Stop')
    • Fase: A — Severità: N/A
    • Mitigazione: sparisce in Python.
    • Owner: dichiarato in A5.
  • Cutover gap window (job in errore tra stop Win e start Linux)
    • Fase: B6 — Severità: Media
    • Mitigazione: finestra a zero traffic + Gitea "paused" preventivo.
    • Owner: B6 attività #2.
  • Decommissioning prematuro elimina rollback
    • Fase: B8 — Severità: Media
    • Mitigazione: gating ≥1 settimana + ≥1 mese spento prima di riallocare.
    • Owner: B8 attività #3.
  • (integrazione A↔B) Shim PS che assume path Windows si rompe in Fase B
    • Fase: A2, B6 — Severità: Media
    • Mitigazione: shim deve invocare Python via path da env (%CI_PYTHON_VENV%), non hardcoded F:\.
    • Owner: A2 attività #7.
  • (integrazione A↔B) config.toml con default Win-only blocca B1
    • Fase: A1, B1 — Severità: Bassa
    • Mitigazione: A1 deve già includere [paths.linux] con default POSIX.
    • Owner: A1 attività #4.
  • (integrazione A↔B) Test pytest assumono Path('F:\\CI') e falliscono su Linux
    • Fase: A1, B1 — Severità: Media
    • Mitigazione: fixture parametrizzata tmp_path in tutti i test, no path hardcoded.
    • Owner: A1 attività #11.
  • Licenza Workstation Pro Linux cambia post-Broadcom
    • Fase: B1 — Severità: Bassa
    • Mitigazione: free per uso personale e commerciale dal 2024; monitorare; piano B = KVM (rimandato).
    • Owner: B1 monitoring continuo.

7. Definizione di "fatto" globale (A+B)

  • Tutti gli script scripts/*.ps1 portati a Python o ridotti a shim (eccetto template/*.ps1 e Install-CIToolchain-*.ps1 che restano PS by design)
  • Invoke-CIJob.ps1 non più referenziato nei workflow YAML (action invoca la CLI Python)
  • pytest verde con coverage ≥70% su src/ci_orchestrator/
  • ruff check + mypy --strict puliti su tutto src/
  • lint.yml dual-stack (PSSA per PS legacy + ruff/mypy/pytest per Python) PASS
  • act_runner gira come act-runner.service systemd su Linux Mint
  • Tutti i template VM (WinBuild2025, WinBuild2022, LinuxBuild2404) operativi dal nuovo host con snapshot integri
  • Workflow build-ns7zip.yml matrix Win+Linux PASS dal nuovo host
  • Burn-in capacity 4 job concorrenti × 10 round PASS, tempi entro ±20% baseline Windows
  • Storage migrato a /var/lib/ci/, host Windows spento come rollback
  • Tutti i timer systemd attivi e schedulati equivalenti ai Task Scheduler Windows
  • Credenziali guest accessibili da ci-runner headless (strategia documentata)
  • README.md, AGENTS.md, docs/ARCHITECTURE.md, docs/HOST-SETUP.md, docs/CI-FLOW.md, docs/RUNBOOK.md aggiornati allo stato finale
  • ≥1 settimana di esercizio in produzione su host Linux senza incidenti
  • Backup F:\CI\ archiviato e validato
  • Hook §4 (Protocol VmBackend, factory backend, naming neutro) presenti nel codice e referenziati in docs/ARCHITECTURE.md

7.1 Auto-check

Domande di auto-verifica a cui il documento risponde "sì":

  • Ogni voce della checklist master compare almeno una volta come - [ ] dentro lo step corrispondente? Sì: voci [A1]…[A5] mappate alle attività in §1, [B1]…[B8] in §2, [X1]…[X4] in §3.
  • Ogni step A<n>/B<n>/X<n> ha tutte le sottosezioni richieste? Sì: Obiettivo, Input/dipendenze, Attività, Hook futuri Fase C, Test/validazione, Rollback, Definizione di fatto presenti per ogni step (B*: in più rischi specifici).
  • I 12 errori frequenti di AGENTS.md sono stati indirizzati o dichiarati non applicabili?
    • #1 (sintassi PS 7+): si applica solo agli shim PS residui in A2/A3 — vincolo PS 5.1 mantenuto
    • #2 ($LASTEXITCODE non controllato): N/A in Python (subprocess.returncode esplicito)
    • #3 (path hardcoded): risolto da A1 attività #4 (env vars + config.toml)
    • #4 (mix branch WinRM/SSH): risolto dalla separazione transport/winrm.py vs transport/ssh.py
    • #5 (WinRM verso Linux): impossibile per design — backend selezionato per guest_os
    • #6 (seed ISO Linux): preservato nei template, non toccato in A/B
    • #7 ([ordered]@{} PS 2): N/A
    • #8 (ForEach-Object -Parallel): N/A in Python (concorrenza via concurrent.futures o asyncio se serve)
    • #9 (snapshot powered-off): coperto da test pytest in A5 + procedura B2 attività #1
    • #10 (vmrun getGuestIPAddress): coperto da test pytest in A1 attività #11
    • #11 (machine-id Linux): preservato nel template, citato in B2 e in §6 rischi
    • #12 (stderr nativa + 'Stop'): N/A in Python, dichiarato in A5 e §6
  • Gli hook §4 sono coerenti con idea-3-esxi-support.md §3? Sì: Protocol VmBackend ha esattamente i metodi clone_linked, start, stop, delete, get_ip previsti dalla firma EsxiBackend; selettore [backend] in config.toml come da §3 di idea-3.

8. Riferimenti