File size: 6,499 Bytes
628d92a
 
 
 
7d5b986
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628d92a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# Procédure de release

> Sprint A9 du plan de remédiation institutionnelle
> ([`docs/audits/remediation-plan-2026-05.md`](../audits/remediation-plan-2026-05.md)).
> Étendu au Sprint S6 (déploiement institutionnel BnF).

## Pré-requis avant tout tag (Sprint S6)

| Vérif | Commande | Cible |
|---|---|---|
| Tests verts | `pytest tests/ -q` | 0 failed |
| Lint propre | `ruff check picarones/ tests/` | All checks passed |
| Type strict | `python -m mypy picarones/domain/` | 0 erreur |
| Sécurité statique | `bandit -ll -r picarones/` | 0 HIGH |
| CVEs deps | `pip-audit` | aucune CVE non-mitigée dans le runtime |
| CHANGELOG | section `## [X.Y.Z] — YYYY-MM-DD` présente | |
| Compteurs doc | `python scripts/gen_readme_tables.py --check` | exit 0 |

## Mode public vs institutionnel

Pour un déploiement BnF (mode institutionnel), s'assurer que les
variables d'environnement de production sont prêtes **avant** de
tagger.  L'app refuse de démarrer sans (Sprint S6.9).

| Variable | Public (HF Space) | Institutionnel |
|---|---|---|
| `PICARONES_PUBLIC_MODE` | `1` | non set |
| `PICARONES_CSRF_REQUIRED` | non set | `1` |
| `PICARONES_CSRF_SECRET` | non set | **OBLIGATOIRE** |
| `PICARONES_LOG_FORMAT` | non set | `json` (recommandé pour ops) |

### Génération du CSRF secret

```bash
# 32 bytes hex
openssl rand -hex 32

# Persister dans le secret manager institutionnel :
#   - Vault : ``vault kv put secret/picarones csrf=<hex>``
#   - AWS Secrets Manager : ``aws secretsmanager create-secret``
#   - Kubernetes : ``kubectl create secret generic picarones-csrf``
#   - Docker Compose : ``.env`` non versionné
```

**Ne JAMAIS** committer ce secret dans un Dockerfile, un
docker-compose.yml versionné, ou un dépôt git public.

## Vue d'ensemble

Une release Picarones produit **trois artefacts** :

1. Un wheel + sdist sur **PyPI** (`pip install picarones==X.Y.Z`).
2. Une image Docker **multi-arch** sur ghcr.io
   (`docker pull ghcr.io/maribakulj/picarones:X.Y.Z`).
3. Une **GitHub Release** avec le sdist/wheel attachés et les
   release notes extraites du `CHANGELOG.md`.

Le pipeline est entièrement automatisé : il suffit de pousser un
tag `v*.*.*` pour déclencher l'enchaînement complet (workflow
[`.github/workflows/release.yml`](../../.github/workflows/release.yml)).

## Procédure release standard

### Pré-requis (une fois)

1. **PyPI Trusted Publisher** : sur <https://pypi.org/manage/account/publishing/>,
   ajouter ce repo + workflow `release.yml` + environnement `pypi`.
   Idem pour TestPyPI dans l'environnement `testpypi`.
2. **GitHub repo** : créer les environnements `pypi` et `testpypi`
   dans Settings → Environments, et marquer `pypi` comme "required
   reviewers" si vous voulez une validation manuelle finale.
3. **GHCR** : `packages: write` sur `GITHUB_TOKEN` est natif (rien
   à configurer).

### Cycle de release

```bash
# 1. Vérifier que main est vert + à jour
git checkout main
git pull --ff-only

# 2. Mettre à jour le CHANGELOG.md (Keep a Changelog)
#    Ajouter une section ## [1.2.0] — YYYY-MM-DD avec les changes
git add CHANGELOG.md
git commit -m "docs(changelog): release 1.2.0"

# 3. Tag annoté + push
git tag -a v1.2.0 -m "Picarones 1.2.0"
git push origin main
git push origin v1.2.0

# 4. Surveiller le workflow Actions
gh run watch
```

Le workflow déroule **automatiquement** :

1. **build** — sdist + wheel via setuptools_scm (version dérivée du tag),
   `twine check`, smoke test wheel install.
2. **publish-testpypi** — upload TestPyPI via OIDC trust.
3. **testpypi-smoke** — installation depuis TestPyPI dans un container
   vierge + `picarones demo`.
4. **publish-pypi** — upload PyPI via OIDC trust (production).
5. **docker** — build multi-arch (linux/amd64 + linux/arm64) avec
   QEMU, push ghcr.io, attestations SLSA + SBOM.
6. **github-release** — création de la Release GitHub avec corps
   extrait depuis la section CHANGELOG correspondante.

Durée totale : ~15 min (multi-arch + 30s d'indexation TestPyPI).

## Versionnement

Picarones suit **Semantic Versioning 2.0.0** :

- `MAJOR.MINOR.PATCH` — incompatibilité, ajout, fix.
- Suffixes pré-release : `-rc1`, `-beta1`, `-alpha1`. Le workflow
  les détecte et coche `prerelease=true` sur la GitHub Release.

`setuptools_scm` dérive automatiquement la version du tag git :

| Contexte | Version produite |
|---|---|
| Tag `v1.2.0` | `1.2.0` |
| 5 commits après `v1.2.0` | `1.2.1.dev5+g<sha>` (dev seulement) |
| `v1.3.0-rc1` | `1.3.0rc1` (PEP 440) |

## Procédure d'urgence : hotfix sécurité

Pour un fix CVE qui doit sortir en < 72 h (politique GOVERNANCE.md) :

```bash
git checkout -b hotfix-cve-2026-XXXX main
# correctif minimal + test
git commit -m "fix(security): patch CVE-2026-XXXX"
# CHANGELOG bump
git commit -m "docs(changelog): release 1.2.1"
git tag -a v1.2.1 -m "Picarones 1.2.1 (security)"
git push origin hotfix-cve-2026-XXXX v1.2.1
# Le workflow release.yml gère le reste.
# Après merge : annonce sur SECURITY.md + courriel mainteneur.
```

## Yanking d'une release publiée

PyPI permet de retirer (yank) une version compromise sans la
supprimer. À utiliser si une release introduit une régression
critique :

```bash
# Connexion à PyPI → Manage → version concernée → "Yank"
# Justification dans le commentaire (visible publiquement).
```

L'image ghcr.io reste — mais le tag `:latest` ne pointera plus vers
la version yankée si on pousse une nouvelle release.

## Validation post-release

Checklist 30 min après la fin du workflow :

- [ ] `pip install picarones==<version>` fonctionne dans un venv frais.
- [ ] `docker run ghcr.io/maribakulj/picarones:<version>` démarre
      sans erreur et expose `/health`.
- [ ] La GitHub Release affiche bien les release notes attendues.
- [ ] `cffconvert --validate` confirme que `CITATION.cff` cite la
      bonne version (Sprint A12+).

## Annexe : rollback complet

Si la release est compromise et doit être retirée intégralement :

1. PyPI : yank la version (cf. plus haut).
2. ghcr.io : `docker manifest rm ghcr.io/maribakulj/picarones:<version>`.
3. GitHub Release : passer en draft + ajouter un README explicatif.
4. Tag git : `git push --delete origin v<version>` puis nouveau
   tag `v<version>+1` corrigé (un tag git ne peut pas être réécrit
   sans casser tous les checkouts existants — préférer le bump).

Ne **jamais** force-push un tag déjà publié — les utilisateurs qui
ont fait `git fetch` voient un conflit.