Découpage opérationnel du prd.md. Une story = une unité livrable, testable, qui laisse la CLI dans un état utilisable.
| Lot | Fichier | Chapitres du manuel | Stories | Thème |
|---|---|---|---|---|
| M0 | M0-socle.md | 00, 01 | PVX-001 → 009 | Squelette Go, config, auth, TLS, premières lectures |
| M1 | M1-lecture.md | 02 | PVX-010 → 017 | Inventaire read-only complet, rendus, harness de test |
| M2 | M2-taches-et-etats.md | 03 | PVX-018 → 023 | UPID, polling, garde-fous d'écriture, start/stop |
| M3 | M3-cycle-de-vie.md | 04, 05 | PVX-024 → 031 | create / clone / set / snapshot / template / rm |
| M4 | M4-acl-et-securite.md | 02, 09 | PVX-032 → 036 | Users, tokens, ACL, diagnostic du 403 |
| M5 | M5-backup-et-pra.md | 07 | PVX-037 → 040 | vzdump, restore, exercice PRA chronométré |
| M6 | M6-iac.md | 08, 11, 12, 13 | PVX-041 → 048 | Inventaire dynamique, drift, adopt, wrappers TF/Ansible |
| M7 | M7-finition.md | 09, 10 | PVX-049 → 055 | Réseau, pools, migration, complétion, CI, release |
55 stories. Aucun lot ne démarre avant que la preuve de fin du lot précédent ne soit obtenue (règle du manuel).
Ces fichiers sont la référence détaillée du backlog. Le suivi vivant — statut par story, journal d'apprentissage — se tient hors dépôt.
M0 ── socle ──────────────────────────────────────────────┐
│ 001 bootstrap │
│ └─002 config ──003 client HTTP ──004 TLS │
│ └─007 erreurs ──005 version │
│ └─006 node ──008 doctor │
│ 009 verbose+redaction │
▼ │
M1 ── lecture ────────────────────────────────────────────┤
│ 010 renderers ──011 vm ls ──013 show │
│ 012 lxc ls 014 storage 015 task 016 cluster │
│ 017 API-MAP + harness de test (transverse) │
▼ │
M2 ── écriture sûre ──────────────────────────────────────┤
│ 018 UPID ──019 poller ──021 pipeline mutation │
│ 020 dry-run/confirm ────────┘ └─022 vm status │
│ └─023 lxc status │
▼ │
M3 ── cycle de vie ───────────────────────────────────────┤
│ 024 clone 025 create 026 set 027 template │
│ 028 snapshot 029 agent 030 lxc CRUD 031 rm+managed │
├──────────────┬──────────────────────────────────────────┤
▼ ▼ │
M4 ACL M5 backup ─── (indépendants entre eux) │
032→036 037→040 │
└──────┬───────┘ │
▼ │
M6 ── IaC (nécessite M3 + M5) ─────────────────────────────┤
041 garde managed 042 inventory 043 tfstate 044 drift │
045 adopt 046 plan/apply 047 configure 048 E2E │
▼ ▼
M7 ── finition ───────────────────────────────────────────┘
049 net 050 pool 051 storage upload 052 migrate
053 completion 054 CI 055 release
### PVX-0XX — Titre à l'infinitif
**Taille** S | M | L **Type** R (lecture) · W (écriture) · W‼ (destructif/sensible) · ⚙ (infra projet)
**Dépend de** : … **PRD** : §…
En tant qu'… / je veux … / afin de …
**Critères d'acceptation** (Étant donné / Quand / Alors)
**Preuve** commande à exécuter et sortie attendue
**Ce que ça doit t'apprendre** l'objectif pédagogique réel de la story
Une story n'est close que si tous ces points sont vrais :
- Schéma vérifié — l'endpoint appelé a été confronté à l'API viewer officiel ou à
search-pve-api.ts; chemin + source ajoutés àdocs/API-MAP.md. Aucun endpoint écrit de mémoire (PRD §6.3). - Tests — au moins un test unitaire ou un test client sur
httptestavec fixture danstestdata/.make test lintvert. - Écritures — si la story est de type W :
--dry-runimplémenté, confirmation en place, séquence pre-read → plan → gate → write → poll → post-read respectée (PRD §5.3). - Secrets — aucune fuite en sortie standard, en trace
--verboseou en message d'erreur. - Journal — une entrée dans
docs/LEARNING-LOG.md: quel endpoint, quelle surprise, quelle erreur commise, quelle règle retenue.
| Lot | Preuve de fin de lot | État |
|---|---|---|
| M0 | pvecli version renvoie la version réelle du nœud, TLS vérifié, token non-root |
☑ obtenue — PVE 9.2.2, empreinte épinglée, automation@pve!pvectl |
| M1 | pvecli vm ls -o json | jq '.[].name' fonctionne ; API-MAP.md complet |
☑ obtenue — et API-MAP.md est vérifié par un test, pas par relecture |
| M2 | Un stop affiche l'UPID, attend l'exitstatus, relit l'état — sur un LXC Nginx |
☑ obtenue — pvecli lxc stop 120 : UPID vzstop, exitstatus, relecture à stopped |
| M3 | Template cloud-init créé puis cloné intégralement par pvecli, sans l'UI |
☑ obtenue — template 9000 construit, cloné en 212, clone joignable en SSH ; garde managed opposée à vm rm 212 |
| M4 | Un 403 provoqué, diagnostiqué via access whoami, corrigé par ACL et documenté |
☑ obtenue — token jetable sans ACL → 403 ; whoami --can dit lequel ; une ACL ciblée sur /vms/120 corrige ; token révoqué |
| M5 | VM détruite puis restaurée par pvecli, RPO/RTO mesurés |
☑ obtenue — VM 212 sauvegardée, détruite, restaurée, service rendu. RPO 19 s (prouvé par un fichier écrit après l'archive et absent au retour), RTO 20 s |
| M6 | Chaîne TF → iac inventory → Ansible → page Nginx servie ; iac drift détecte une modif UI |
☑ obtenue — Terraform crée 210 en 23 s, l'inventaire trouve son IP par l'agent, Ansible sert Nginx natif sur :80 et Caddy conteneurisé sur :8080, idempotence mesurée (changed=0 au 2ᵉ passage) et contenu vérifié — pas seulement le code HTTP. Un memory=3072 posé hors Terraform est attrapé par iac drift puis résorbé par iac apply |
| M7 | Binaire installé et utilisable depuis le nœud ; complétion zsh opérationnelle | ☑ obtenue — make release VERSION=v0.1.0 puis make install-node ; depuis le nœud, PVE_API_URL=https://localhost:8006 pvecli doctor répond quatre ✓ après un pvecli config trust local. Le certificat porte CN=pve.example : ni l'IP du poste ni localhost n'y correspondent, et c'est l'épinglage d'empreinte — indépendant du nom — qui rend le cas trivial. Complétion zsh générée, chargée, et proposant les VMID avec leur nom |
55 stories terminées sur 55. M0 à M7 sont clos.
| Lot | Fait | Reste |
|---|---|---|
| M0 | 001 → 009 | — |
| M1 | 010 → 017 | — |
| M2 | 018 → 023 | — |
| M3 | 024 → 031 | — |
| M4 | 032 → 036 | — |
| M5 | 037 → 040 | — |
| M6 | 041 → 048 | — |
| M7 | 049 → 055 | — |
M4 a soldé une dette de M0 : le message d'erreur d'un 403 renvoyait déjà, en
dur, vers un pvecli access whoami qui n'existait pas. Il existe.
Il a aussi soldé une dette de méthode. Le token avait été élargi trois fois à la
main sur le nœud (import-from, SDN.Use, puis /access pour ce lot) ; c'est
désormais pvecli access acl set qui fait le geste — sauf le dernier, qui ne
pouvait pas venir de l'outil : accorder le droit d'accorder des droits doit être
fait par ailleurs.
M7 a rencontré la même limite deux fois de plus, et l'a documentée plutôt que de
la contourner. Pool.Allocate et Datastore.AllocateTemplate ont dû être posés
depuis le nœud, parce que le token n'a pas Permissions.Modify et qu'on ne peut
pas accorder un privilège qu'on ne détient pas. Le refus a été capturé avant le
geste : pvecli access acl set --path /pool … répond
403 — Permission check failed (/pool, Permissions.Modify).
Et Sys.Modify sur /nodes/pve est resté volontairement refusé : c'est le
privilège de net apply, la seule commande qui peut rendre le nœud injoignable,
sans accès console pour se rattraper.
État du lab, entièrement construit par pvecli :
Les deux VM et le conteneur sont joignables en SSH avec la clé injectée à la
création. Le pool lab existe surtout comme chemin d'ACL : /pool/lab couvre
ses membres présents et à venir.
La plage 900-999 reste réservée aux tests d'intégration (make integration),
qui créent, démarrent, arrêtent et détruisent la VM 990 à chaque exécution et
ne touchent jamais rien en dehors.
