# A4 — Closeout Fase A4 di `plans/implementation-plan-A-B.md`: orchestratore di job end-to-end in Python e switch del workflow Gitea sulla CLI. Branch: `feature/python-rewrite-phase-a`. ## Stato attuale - [x] `commands/job.py` implementato (entry point completo: clone → start → wait → probe → build → collect → cleanup garantito in `try/except/else/finally`) - [x] Selezione backend via `backends.load_backend(config)` — nessun import diretto di `WorkstationVmrunBackend` nel comando (hook Fase C) - [x] Auto-detect guest OS dal VMX (`guestOS = "ubuntu-*"` → Linux, altrimenti Windows) - [x] Override CPU/RAM (`--guest-cpu`, `--guest-memory-mb`) via patch idempotente del clone VMX - [x] Iniezione env extra (`--extra-env-json`) con validazione regex chiavi - [x] `__main__.py` registra il sottocomando `job` - [x] `scripts/Invoke-CIJob.ps1` ridotto a shim PS 5.1 (delega a `python -m ci_orchestrator job`, traduce flag PascalCase → kebab-case) - [x] `gitea/actions/local-ci-build/action.yml` invoca direttamente `& $venvPython -m ci_orchestrator job ...` (più nessuno `@params` splatting verso lo shim PS) - [x] `runner/config.yaml` esporta `PYTHONIOENCODING=utf-8` per garantire stdio UTF-8 nei log act_runner → Gitea - [x] `tests/python/test_commands_job.py` aggiunto: 13 test (happy path Linux, happy path Windows + override, skip-artifact, cleanup su build failure, cleanup su clone failure, validazione template/extra-env, helper `_apply_vmx_overrides` / `_read_guest_os_from_vmx` / `_parse_extra_env_json`) - [x] Suite completa: ruff clean, mypy --strict clean, **104 pytest PASS**, coverage totale **79.25%**, coverage `commands/job.py` **83%** - [ ] Workflow `build-ns7zip.yml` matrix (Win + Linux) end-to-end PASS contro VM reali — richiede host con runner registrato - [ ] Workflow `self-test.yml` / `lint.yml` PASS sul nuovo entry point — stessa dipendenza - [ ] Misurazione tempo job entro ±10% del baseline PowerShell (`Measure-CIBenchmark.ps1`) ## Definizione di "fatto" A4 | Criterio | Stato | | --------------------------------------------------- | ------------------------------------ | | `action.yml` chiama Python direttamente | ✅ | | Shim `Invoke-CIJob.ps1` preservato per uso manuale | ✅ | | `runner/config.yaml` ha `PYTHONIOENCODING=utf-8` | ✅ | | Coverage `commands/job.py` ≥80% | ✅ 83% | | Cleanup VM garantito su tutti i path di errore | ✅ (test `test_job_cleanup_on_*`) | | Hook Fase C (factory backend, no import workstation diretto) | ✅ | | Workflow `build-ns7zip.yml` matrix PASS | ⏳ validazione hardware utente | | Tempo entro ±10% baseline | ⏳ misurazione utente | ## Cosa resta a carico utente 1. Eseguire `build-ns7zip.yml` (matrix Win + Linux) sul runner host registrato e verificare che il job concluda con artifact pubblicato — il flusso deve passare dal nuovo entry point Python. 2. Lanciare `self-test.yml` e `lint.yml` per validare che gli altri workflow restino verdi dopo lo switch. 3. Eseguire `scripts/Measure-CIBenchmark.ps1` (resta in PS) prima/dopo lo switch e confermare delta tempo entro ±10%. 4. Aggiornare `Measure-CIBenchmark.ps1` se la wall-clock del nuovo entry point la rende necessaria; in caso di regressione >10%, aprire issue in `TODO.md` per profiling A5. 5. Coverage `commands/job.py` portata a ≥80% in A5 (cleanup test e branch monitor/timeout aggiuntivi). ## Riferimenti commit - `feat(a4): port Invoke-CIJob.ps1 to ci_orchestrator job` — `commands/job.py`, `__main__.py`, shim `scripts/Invoke-CIJob.ps1`, `tests/python/test_commands_job.py` - `ci(a4): switch local-ci-build action.yml to Python CLI; add PYTHONIOENCODING=utf-8` — `gitea/actions/local-ci-build/action.yml`, `runner/config.yaml` - `docs(a4): mark A4 as code-complete; add A4-closeout.md` — `plans/implementation-plan-A-B.md`, `plans/A4-closeout.md`