RUSTWARDEN_EVOLUTION_PROCESS.md 19462 octets

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.md du 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Test en tant que documentation : tout nouvel API publique est livree avec un exemple dans la rustdoc, execute comme doctest.
  6. 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 pub exporte 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

  1. Bump Cargo.toml a vX'.Y'.Z'.
  2. cargo fix --edition-idioms handles most of it.
  3. Manual: pour chaque appel a old_api, decider si NewConfig::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" :

Critere1 (mauvais)3 (neutre)5 (excellent)
GeneraliteSert 1 seul consommateur, logique app-specifiqueSert au moins 1 consommateur et anticipe >=1 autreResout un besoin architectural commun a toute app d'auth
Stabilite APIBreaking change necessaireNon-breaking avec feature flagNon-breaking par ajout pur
PerformanceOverhead mesure > 5% sur hot pathOverhead imperceptibleAmelioration mesurable
SecuriteRegression possible sans mitigationNeutre vs l'existantHardening net (fix faille, defense-in-depth)
Complexite impl> 500 lignes, requiert unsafe/refactor majeur< 200 lignes, patterns existants reutilises< 50 lignes, trivial
Charge maintenanceDependance externe non maintenue, cas edge nombreuxNormale (1 mainteneur peut gerer)Zero : pure composition de briques stables
DocumentationPas d'exemple, pas de rustdoc, pas de CHANGELOGRustdoc + exemple inlineRustdoc + 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 :

  1. Birth (minor release) : la feature sort en experimental-*, non incluse par defaut. Les consommateurs opt-in dans leur Cargo.toml.
  2. Adoption : suivi dans le CHANGELOG — "X apps ont active experimental-ssr-forms".
  3. 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.
  4. 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 pub exportes par le lib.rs du 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::NotFound par exemple).
  • Codes HTTP retournes par les handlers "happy path".

Non-stables (peuvent changer en minor)

  • Items exportes dans pub(crate) ou derriere pub mais dans des modules internal/*, 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 :

CategorieObligatoire ?Contenu minimum
UnitOuiHappy path + 3 cas d'erreur par fonction publique
DoctestOuiAu moins 1 exemple copie/collable par item publique
IntegrationSi touche service/middlewareTest avec DB in-memory (sea-orm mock ou sqlite) ou client axum fake
CompatSi touche API existanteLe code consommateur pre-RFC compile sans warning nouveau
BenchSi touche hot pathComparison avant/apres, regression > 10% = justification ecrite
FuzzSi touche parsing/deserialisationcargo 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 :

  1. Rustdoc sur chaque nouvel item public, avec exemple testable.
  2. Entree CHANGELOG.md dans la categorie correcte (Added / Changed / Deprecated / Removed / Fixed / Security).
  3. Mise a jour du README.md si la feature etend les cas d'usage documentes.
  4. Guide de migration (dans le CHANGELOG ou fichier dedie) si la feature remplace une API existante.
  5. 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

TypeFrequenceContenu
Patch x.y.Z+1A la demande (bug ou securite)Fixes sans API change, pas de nouvelle feature
Minor x.Y+1.0Toutes les 2 semainesFeatures non-breaking accumulees depuis la derniere minor
Major X+1.0.0Tous les 6-12 moisDepreciations echues, breaking changes, nouveaux defauts

Checklist de release

  • CHANGELOG.md a jour avec toutes les entrees depuis la release precedente.
  • Tests : cargo test --all-features passe.
  • Lint : cargo clippy --workspace --all-features -- -D warnings passe.
  • Doc : cargo doc --no-deps sans warning.
  • Benchs : pas de regression > 10% non justifiee.
  • Tag git annote : v0.8.0.
  • GitHub Release avec les notes du CHANGELOG.
  • cargo publish sur 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.md de 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 :

  1. Sous 24h : cargo yank sur la version defectueuse (empeche les nouvelles installs).
  2. Sous 48h : patch release x.y.Z+1 avec le fix, CHANGELOG clairement marque "Security" ou "Critical fix".
  3. 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 :

  1. Ecrire un workaround local (comme admin_csrf_protection cote gitrust).
  2. Documenter dans son UPSTREAM_<framework>.md : contexte, workaround, propositions pour le framework.
  3. 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.
  4. 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

SujetCanal
Discussion RFCIssue GitHub upstream avec label rfc
Question usageREADME + rustdoc ; en derniere instance issue label question
Bug reportIssue GitHub upstream avec label bug + reproducer minimal
Coordination consommateursPull 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: bool dans une config optionnelle de csrf_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 :

DateAuteurChangement
2026-04-21EBO (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.