Rustwarden-core — Processus generique d'evolution du framework
Ce document definit une methodologie agnostique de l'application consommatrice pour proposer, evaluer, livrer et retirer des changements sur le framework rustwarden-core. Il s'adresse a la fois aux mainteneurs upstream et aux apps downstream (gitrust aujourd'hui, d'autres a venir).
Non-buts :
- Ce n'est pas un catalogue des APIs actuelles (cf
README.mddu framework). - Ce n'est pas le backlog des improvements specifiques a gitrust (cf
UPSTREAM_RUSTWARDEN.md). - Ce n'est pas un guide utilisateur (cf
docs/SERVICES_REFERENCE.md).
Le document vit dans gitrust parce que c'est actuellement le seul consommateur, mais son contenu doit rester generique. Quand un deuxieme consommateur apparait, ce fichier doit migrer dans le depot du framework (ou ses copies restent synchronisees via un simple git diff).
1. Principes directeurs
- SemVer strict : tout changement d'API publique respecte
MAJOR.MINOR.PATCH. Un renommage public, un ajout de parametre obligatoire, une modification du comportement par defaut sont breaking meme si le code compile. - Backwards-compat par defaut : quand deux solutions techniques sont equivalentes, on choisit celle qui preserve les signatures existantes. Une nouvelle signature vit a cote de l'ancienne, l'ancienne est
#[deprecated]sur au moins 3 mineures avant suppression. - Lowest common denominator : le framework ne doit pas embarquer de logique specifique a une app. Si une feature n'a qu'un seul consommateur identifie, elle ne rentre pas dans le framework — elle reste dans l'app ou dans une extension.
- Zero-surprise : un changement qui laisse silencieusement casser un flux existant (ex: token vide qui genere 403 sans erreur lisible) est bloquant jusqu'a clarification.
- Test en tant que documentation : tout nouvel API publique est livree avec un exemple dans la rustdoc, execute comme doctest.
- Multi-tenant neutre : le framework ne suppose ni une app SSR, ni une app SPA, ni une app API-only. Chaque API doit soit fonctionner dans les 3 contextes, soit etre clairement scopee (ex: feature flag).
2. Quand une RFC est obligatoire
Une RFC (Request For Comments — un document formalise suivant le template section 3) est obligatoire pour tout changement qui remplit au moins un critere :
-
Ajout, modification ou suppression d'un item
pubexporte par le crate. - Changement d'un comportement par defaut (y compris backwards-compat, car les consommateurs peuvent dependre du defaut).
-
Ajout d'une nouvelle dependance directe dans
Cargo.toml. - Introduction d'un nouveau middleware, service ou trait que plusieurs apps vont consommer.
- Changement du schema DB ou ajout d'une migration.
- Modification du format serialise (cookies, JWT claims, events JSON, metadata).
- Changement de la politique de securite (ex: nouveau hash, nouveau flow d'auth, nouveau mode CSRF).
Pas de RFC (PR direct suffit) :
- Fix d'un bug sans changement d'API.
- Mise a jour de la doc ou du CHANGELOG.
- Refactor interne preservant toutes les signatures publiques.
- Bump de version d'une dependance en patch/minor.
- Ajout de test.
En cas de doute : ecrire la RFC. Le cout d'une RFC prematuree est tres faible ; le cout d'un breaking change accidentel est tres eleve.
3. Template RFC
Chaque proposition est un fichier unique docs/rfcs/NNNN-slug.md dans le depot du framework. NNNN est un numero incremental (0001, 0002, ...), slug est un identifiant court kebab-case.
# RFC NNNN — <Titre court> - **Auteur** : <nom + repo d'origine> - **Date** : YYYY-MM-DD - **Statut** : Draft | Under Review | Accepted | Rejected | Superseded by #MMMM - **Apps consommatrices concernees** : <liste des apps qui beneficient directement> ## Summary (1 paragraphe) Que propose-t-on ? En une phrase qu'un dev non-specialiste doit pouvoir comprendre. ## Motivation - Quel probleme concret cette RFC resout-elle ? - Qui est impacte aujourd'hui (apps consommatrices, cas d'usage) ? - Quelles sont les consequences si on ne fait rien ? - Y a-t-il un delai/urgence (incident securite, deadline upstream) ? ## Non-goals (explicite) Lister ce qui est **hors scope** pour eviter le scope creep. Ex: "cette RFC ne touche pas au flow OAuth2, qui est traite dans la RFC #XXXX". ## API proposee Code Rust complet des nouvelles signatures publiques. Exemple d'utilisation cote consommateur (copie/collable dans un handler/service d'une app fictive). ```rust // Signature complete pub fn new_api(...) -> ... { ... } // Exemple d'usage async fn handler(...) -> Response { let result = new_api(...)?; // ... }
Analyse de compatibilite
Checklist :
- API existante non modifiee → non-breaking (patch/minor possible).
- API existante etendue avec defaut → non-breaking (minor).
- API existante modifiee ou supprimee → breaking (major requis + guide migration).
- Nouveau behavior par defaut → evaluer l'impact sur les consommateurs, feature-flagger si risque.
- Changement de schema DB → prevoir une migration reversible.
- Changement de format serialise → prevoir un migrateur de donnees persistantes existantes.
Alternatives considerees
Minimum 3 alternatives avec analyse pros/cons, y compris "ne rien faire". Sans ca la RFC est incomplete : on ne peut pas juger une proposition sans son cout relatif.
Alternative A (recommandee) : ...
Pros : ... Cons : ...
Alternative B : ...
Pros : ... Cons : ...
Alternative C (ne rien faire / garder le workaround) : ...
Pros : zero cout, zero risque. Cons : dette accumulee dans les apps consommatrices, risque de divergence.
Testing plan
Tests unitaires obligatoires :
- Happy path (au moins 1).
- 3 cas d'erreur minimum (input invalide, contrainte violee, timeout/panic path).
Tests d'integration si la RFC touche :
- Un service : test avec DB in-memory.
- Un middleware : test avec client axum fake.
- Une migration : test up/down avec verif des donnees existantes.
Tests de non-regression :
- Code consommateur pre-RFC doit encore compiler sans warning.
Benchs si sensitive-perf :
- Comparaison avant/apres.
- Regression > 10% bloque le merge sans justification ecrite.
Migration guide
Section a copier telle quelle dans le CHANGELOG.md au moment du merge. Format :
### API changes **Before (vX.Y.Z)** : ```rust old_api(foo, bar);
After (vX'.Y'.Z') :
new_api(foo, bar, NewConfig::default());
Migration steps
- Bump
Cargo.tomla vX'.Y'.Z'. cargo fix --edition-idiomshandles most of it.- Manual: pour chaque appel a
old_api, decider siNewConfig::default()convient.
## Open questions Liste des points en suspens. La RFC ne peut pas etre acceptee tant qu'ils ne sont pas resolus : - [ ] Question 1 - [ ] Question 2 ## Tracking issue Lien vers l'issue GitHub ou la RFC est discutee.
4. Grille d'evaluation
Une fois la RFC deposee, elle est notee sur 7 criteres (1-5 chacun, 35 max). Minimum 25/35 pour passer en "Accepted" :
| Critere | 1 (mauvais) | 3 (neutre) | 5 (excellent) |
|---|---|---|---|
| Generalite | Sert 1 seul consommateur, logique app-specifique | Sert au moins 1 consommateur et anticipe >=1 autre | Resout un besoin architectural commun a toute app d'auth |
| Stabilite API | Breaking change necessaire | Non-breaking avec feature flag | Non-breaking par ajout pur |
| Performance | Overhead mesure > 5% sur hot path | Overhead imperceptible | Amelioration mesurable |
| Securite | Regression possible sans mitigation | Neutre vs l'existant | Hardening net (fix faille, defense-in-depth) |
| Complexite impl | > 500 lignes, requiert unsafe/refactor majeur | < 200 lignes, patterns existants reutilises | < 50 lignes, trivial |
| Charge maintenance | Dependance externe non maintenue, cas edge nombreux | Normale (1 mainteneur peut gerer) | Zero : pure composition de briques stables |
| Documentation | Pas d'exemple, pas de rustdoc, pas de CHANGELOG | Rustdoc + exemple inline | Rustdoc + exemple + entree CHANGELOG + guide migration |
Un score < 25 n'est pas automatiquement un rejet : c'est un signal qu'il faut reecrire la RFC ou choisir une alternative. Un score >= 30 peut accelerer le merge sans second avis.
5. Strategie de versioning
Feature flags pour APIs experimentales
Toute nouvelle API introduite pour un besoin emergent (ex: seul 1 consommateur identifie) vit derriere un feature flag Cargo :
# rustwarden-core/Cargo.toml [features] default = ["core"] core = [] experimental-ssr-forms = [] # CSRF form body parsing experimental-oauth-github = [] # Provider GitHub OAuth
Cycle de vie d'un feature flag :
- Birth (minor release) : la feature sort en
experimental-*, non incluse par defaut. Les consommateurs opt-in dans leurCargo.toml. - Adoption : suivi dans le CHANGELOG — "X apps ont active
experimental-ssr-forms". - Promotion (minor release) : apres 2+ apps adoptantes et 30 jours sans bug critique, la feature est promue en
default. Les deux noms (experimental-*et nom final) coexistent pendant 1 minor pour la migration. - Cleanup (major release) : le flag
experimental-*est supprime.
Pas de feature pour quelque chose que tout le monde voudrait : directement en default.
Depreciation
#[deprecated( since = "0.8.0", note = "use `csrf_protection_with_config` instead (accepts form body). Will be removed in 1.0." )] pub async fn csrf_protection(...) -> ... { ... }
Regles :
- Duree minimale : N mineures depuis la depreciation (N >= 3). Ex : deprecie en 0.8.0 → supprimable a partir de 1.1.0 minimum.
- Note obligatoire : pointer explicitement vers le remplacement avec exemple.
- CHANGELOG : entree "Deprecated" dans chaque release entre depreciation et suppression.
- Tests : les tests de l'ancienne API continuent de tourner jusqu'a suppression (avec
#[allow(deprecated)]).
Breaking changes
Aggreges dans une release major uniquement :
- Pas de breaking dans une patch (
x.y.Z+1). - Pas de breaking dans une minor (
x.Y+1.0). - Une seule major par 6-12 mois, regroupant plusieurs depreciations echues + nouveaux defauts.
Chaque major est accompagnee d'un guide migration exhaustif (fichier MIGRATION-VX.md) avec tous les changements et leurs remediations.
6. Garanties de compatibilite
Stables (part du contrat public)
- Tous les items
pubexportes par lelib.rsdu crate. - Signatures des handlers (
services/*::*). - Colonnes et types des tables de migration (ajout de colonne optionnel OK, changement ou suppression = breaking).
- Format serialise des cookies de session, JWT claims, events JSON.
- Noms des variantes d'erreur (kind
AppError::NotFoundpar exemple). - Codes HTTP retournes par les handlers "happy path".
Non-stables (peuvent changer en minor)
- Items exportes dans
pub(crate)ou derrierepubmais dans des modulesinternal/*,utils/*,impl/*. - Format des logs
tracing(tags, niveaux). - Ordre des champs dans les erreurs (mais pas leur presence).
- Parametres par defaut non documentes comme API publique.
Quand c'est ambigu : le merge suppose que c'est stable. Un refactor pretendu "interne" mais visible par les consommateurs compte comme breaking.
7. Exigences de tests
Chaque PR implementant une RFC doit livrer :
| Categorie | Obligatoire ? | Contenu minimum |
|---|---|---|
| Unit | Oui | Happy path + 3 cas d'erreur par fonction publique |
| Doctest | Oui | Au moins 1 exemple copie/collable par item publique |
| Integration | Si touche service/middleware | Test avec DB in-memory (sea-orm mock ou sqlite) ou client axum fake |
| Compat | Si touche API existante | Le code consommateur pre-RFC compile sans warning nouveau |
| Bench | Si touche hot path | Comparison avant/apres, regression > 10% = justification ecrite |
| Fuzz | Si touche parsing/deserialisation | cargo fuzz round pendant 10 min sur l'input exterieur |
Les consommateurs (gitrust, autres) peuvent etre invites a tourner leur propre CI contre la version RC du framework avant merge — c'est un "compat test" tiers gratuit.
8. Exigences de documentation
Tout merge inclut :
- Rustdoc sur chaque nouvel item public, avec exemple testable.
- Entree CHANGELOG.md dans la categorie correcte (
Added/Changed/Deprecated/Removed/Fixed/Security). - Mise a jour du
README.mdsi la feature etend les cas d'usage documentes. - Guide de migration (dans le CHANGELOG ou fichier dedie) si la feature remplace une API existante.
- Exemple dans
examples/si la feature introduit un pattern non-trivial (middleware, service compose, flow async).
Pour une app consommatrice, le reflet de ce merge est :
- Bump de version dans son
Cargo.toml. - Mise a jour de son propre fichier
UPSTREAM_<framework>.md(ajout de la section "resolu upstream"). - Eventuelle mise a jour des workarounds internes.
9. Processus de release
Cadence
| Type | Frequence | Contenu |
|---|---|---|
Patch x.y.Z+1 | A la demande (bug ou securite) | Fixes sans API change, pas de nouvelle feature |
Minor x.Y+1.0 | Toutes les 2 semaines | Features non-breaking accumulees depuis la derniere minor |
Major X+1.0.0 | Tous les 6-12 mois | Depreciations echues, breaking changes, nouveaux defauts |
Checklist de release
-
CHANGELOG.mda jour avec toutes les entrees depuis la release precedente. -
Tests :
cargo test --all-featurespasse. -
Lint :
cargo clippy --workspace --all-features -- -D warningspasse. -
Doc :
cargo doc --no-depssans warning. - Benchs : pas de regression > 10% non justifiee.
-
Tag git annote :
v0.8.0. - GitHub Release avec les notes du CHANGELOG.
-
cargo publishsur crates.io. - Issue de tracking ouverte sur chaque app consommatrice listant les PRs d'adoption.
Notification des consommateurs
A chaque release, un commentaire est poste sur :
- Le tracking issue upstream (liste des apps consommatrices).
- Le fichier
UPSTREAM_RUSTWARDEN.mdde chaque app (via PR automatique ou manuelle).
Le consommateur est libre d'adopter la version a son rythme, sauf pour les patches securite ou une fenetre de 14 jours d'adoption est attendue.
10. Strategie de rollback
Si une release contient un bug critique detecte apres publication :
- Sous 24h :
cargo yanksur la version defectueuse (empeche les nouvelles installs). - Sous 48h : patch release
x.y.Z+1avec le fix, CHANGELOG clairement marque "Security" ou "Critical fix". - Sous 7j : post-mortem public sur le tracking issue upstream. Sections : contexte, impact, root cause, timeline, mesures pour eviter recurrence.
Les consommateurs qui ont deja bumpe vers la version yanked doivent eux-memes bumper vers le patch — un message automatise dans leur UPSTREAM_<framework>.md les y invite.
11. Guide des consommateurs
Chaque app consommatrice (gitrust, futurs) est recommandee de :
11.1 Pinning
- Pendant alpha/beta : pin exact
rustwarden-core = "=0.8.3". Evite les auto-updates surprises. - En production stable : pin minor
rustwarden-core = "~0.8.3"(accepte 0.8.x, refuse 0.9.0). Migre a la main sur major.
11.2 Fichier de tracking
Chaque app maintient un docs/UPSTREAM_<framework>.md (cf UPSTREAM_RUSTWARDEN.md dans gitrust). Ce fichier liste :
- Les services framework utilises (catalog).
- Les hooks implementes.
- Les workarounds en cours avec renvoi vers la RFC correspondante.
- Les breaking changes a venir a partir du changelog upstream.
11.3 Contribution
Quand un consommateur rencontre un gap :
- Ecrire un workaround local (comme
admin_csrf_protectioncote gitrust). - Documenter dans son
UPSTREAM_<framework>.md: contexte, workaround, propositions pour le framework. - Deposer une RFC upstream des que le workaround dure > 1 release mineure — l'objectif est que le code du workaround finisse par disparaitre au profit d'une solution framework.
- Contribuer l'implementation si possible. Le framework est plus attractif quand les gros consommateurs y contribuent, et l'app consommatrice beneficie d'une dette reduite.
11.4 Anti-patterns
A eviter cote consommateur :
- Forker le framework : bloque l'adoption des fixes securite upstream. Toujours privilegier un workaround dans le crate de l'app.
- Modifier
crates/rustwarden-core/*: idem, casse la synchronisation. Toute evolution passe par une RFC upstream. - Dupliquer une service du framework dans l'app : dette certaine. Meme si le service upstream est incomplet, l'etendre via wrapper/trait impl plutot que le re-implementer.
- Masquer les workarounds : chaque contournement doit etre documente pour que la dette reste visible.
12. Communication
Canaux officiels
| Sujet | Canal |
|---|---|
| Discussion RFC | Issue GitHub upstream avec label rfc |
| Question usage | README + rustdoc ; en derniere instance issue label question |
| Bug report | Issue GitHub upstream avec label bug + reproducer minimal |
| Coordination consommateurs | Pull request sur UPSTREAM_<framework>.md de chaque app |
Synchro reguliere
Une fois les consommateurs > 1, un sync mensuel entre mainteneurs upstream et consommateurs :
- Revue des RFCs ouvertes.
- Adoption prochaine (qui bumpe quand).
- Backlog partage.
En attendant le second consommateur : la coordination se fait via ce document + UPSTREAM_RUSTWARDEN.md de gitrust.
13. Exemples concrets
Pour illustrer la methode, voici deux exemples tires du contexte actuel.
13.1 Exemple RFC : CSRF compatible SSR form
Le gap "middleware CSRF ne lit pas le form body" (cf UPSTREAM_RUSTWARDEN.md section 1) est un candidat type pour une RFC. La note technique est deja redigee, il suffit de la formaliser :
- Summary : ajouter
accept_form_body: booldans une config optionnelle decsrf_protection. - Motivation : toutes les apps SSR futures auront le meme probleme.
- Alternatives : (A) config + middleware etendu, (B) helper
extract_csrf_from_form, (C) feature flag. - Migration : gitrust bump + suppression de
admin_csrf_protection.
Score prevu : Generalite=5, Stabilite=5 (additif), Perf=4, Secu=5, Complexite=3, Maintenance=4, Doc=5 → 31/35. Accepte direct.
13.2 Exemple NON-RFC : fix typo dans un message d'erreur
Correction de "Invalidd token" → "Invalid token" dans un AppError. PR direct, changelog entry "Fixed". Pas de RFC necessaire.
14. Revisions de ce document
Ce document lui-meme evolue. Chaque changement majeur est trace :
| Date | Auteur | Changement |
|---|---|---|
| 2026-04-21 | EBO (gitrust) | Creation initiale, inspire du processus observe lors de l'iteration CSRF ssh-guard. |
Pour proposer une modification : PR directe sur ce fichier, revue par au moins un mainteneur framework et un mainteneur consommateur.
GitRust