Principes d’ingénierie d’Air
Document fondateur — Version 1.0
Air est un stack d’infrastructure desktop. La qualité de ses fondations détermine la confiance que les développeurs accorderont à l’ensemble. Neuf principes guident toute contribution au code Air. Ils sont distincts de la Charte du projet (qui énonce les valeurs) et des ADRs (qui consignent les décisions techniques) : les Principes d’ingénierie portent sur la méthode, sur comment construire.
Ces principes sont immuables. Les ADRs et spécifications y sont conformes par construction. Tout code intégré au projet Air respecte ces principes.
Principe 1 — Test exhaustif des fondations
Les couches 0 et 1 sont les fondations de tout ce qui est au-dessus. Aucun bug dans ces couches n’est acceptable. Coverage de tests à 100 % (lignes + branches), incluant tests unitaires, property-based testing, tests d’intégration, fuzzing systématique sur toute API qui accepte des données externes (parsing, deserialization, buffers).
Stratégies de test complexes acceptées si nécessaires : harnais de test custom, simulateurs de syscalls pour isoler les tests des conditions kernel, injection de fautes pour valider les chemins d’erreur, tests de stress concurrents. Le temps passé à concevoir une stratégie de test solide est du temps bien investi, jamais perdu.
Les couches supérieures (2 et au-delà) visent un coverage très élevé (> 90 %) mais peuvent avoir une marge selon la nature du code (UI difficile à tester pixel-perfect, par exemple). La rigueur reste, mais l’objectif n’est plus strictement 100 %.
Principe 2 — Arithmétique défensive
Tout calcul numérique est conçu avec conscience de la taille du type. Toute opération qui peut overflow utilise les variantes checked_*, saturating_*, ou wrapping_* explicitement choisies selon la sémantique voulue — jamais l’opération nue qui panique en debug et wrappe silencieusement en release.
Conversion de types numériques : as interdit pour les conversions potentiellement lossy. Utilisation de TryFrom ou try_into qui retourne Result. Les cas où une conversion lossy est intentionnelle doivent être commentés et localisés.
Lints clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_lossless, clippy::arithmetic_side_effects activés en deny dans les couches 0 et 1. Activés en warn minimum dans les couches supérieures.
Principe 3 — Chaînes, buffers, encodages : zéro présomption
Toute manipulation de chaîne distingue explicitement bytes (&[u8], Vec<u8>) et UTF-8 valide (&str, String). Pas de conversion silencieuse. from_utf8 (qui retourne Result) systématique, jamais from_utf8_unchecked sauf dans des cas justifiés et localisés.
Buffers : taille toujours validée avant usage, pas de présomption sur la longueur. Slicing avec get() qui retourne Option plutôt que indexation directe qui peut paniquer. Index toujours bornés et vérifiés.
Encodages : explicites partout. Pas de “string par défaut, l’OS gère”. Pour les chemins de fichiers, distinction explicite Path/OsStr (qui peut contenir non-UTF-8 sur Unix) et str quand on sait que c’est UTF-8. Conversion explicite avec gestion d’erreur.
Parsing de données externes : schema-first quand possible (Cap’n Proto pour AirCom, validation explicite ailleurs). Pas de unwrap() sur des résultats de parsing en code de production.
Principe 4 — Validation en amont, logique en aval
Toute fonction publique valide ses paramètres au début, avant tout traitement. Les invariants attendus sont explicités, vérifiés, et retournent Result (ou Option) en cas de violation. Le corps de la fonction peut alors présumer que les invariants sont respectés et se concentrer sur la logique métier.
Privilégier le pattern « parse, don’t validate » : transformer les entrées en types qui portent la garantie de validité dans leur type, plutôt que valider à plusieurs endroits. Une fonction qui prend un EmailAddress (newtype validé) plutôt qu’un String n’a pas à revalider.
Sur-tester les paramètres est acceptable en couches 0 et 1 ; on optimisera après mesure. Plus jamais de bug parce qu’on a oublié de valider un cas.
Principe 5 — Optimiser après mesure, jamais avant
Les principes 2, 3, 4 produisent du code volontairement défensif. Ce code peut être plus lent et plus verbose que sa version optimiste. C’est assumé.
À chaque fin de cycle (fin de phase, ou jalon majeur), un audit de performance est conduit : profiling sur cas d’usage représentatifs, identification des hotspots, mesure de la part du temps passée dans les validations vs la logique métier. Si — et seulement si — une validation est mesurée comme étant un coût significatif sur un chemin chaud, on peut envisager de la déplacer, l’alléger, ou la supprimer, avec justification écrite et test additionnel garantissant que l’invariant est respecté par construction.
Le sens du flux est : sur-sécuriser puis dégraisser après mesure, jamais sous-sécuriser puis durcir après bug. L’inverse, qu’on voit trop souvent, produit des CVE.
Principe 6 — Règle des 80 % sur les dépendances
Air s’appuie sur des crates Rust existants quand ils sont de qualité, matures, et utiles. Pour chaque dépendance candidate, la règle d’usage est : si moins de 80 % du code de la crate est effectivement utilisé par Air, alors la dépendance est refusée. À la place, on intègre le code strictement nécessaire, on l’adapte au style d’Air, on en fait un sous-module ou une crate Air, qu’on maintient nous-mêmes.
Justification : le code mort dans une dépendance n’est pas neutre. Il continue d’être compilé, doit être maintenu (mises à jour de sécurité, ajustements pour nouvelles versions de Rust), expose une surface d’attaque (CVE sur du code qu’on n’utilise pas mais qui est embarqué), et augmente le binaire. Une dépendance qu’on n’utilise qu’à 10 % est une dette de maintenance à 100 % pour quelqu’un.
Ce principe s’évalue par audit explicite à chaque ajout de dépendance, documenté dans un fichier DEPENDENCIES.md par crate Air : nom, version, % d’API utilisée (estimé), justification. Revue régulière (à chaque fin de phase) pour détecter les dépendances dont l’usage a diminué.
Exceptions explicites possibles pour des crates très matures et structurants où le ratio peut être plus bas si la dépendance est universellement nécessaire et impossible à reproduire raisonnablement. Ces exceptions sont nommées et justifiées dans EXCEPTIONS.md, pas implicites. Exemple consigné : icu4x adopté en exception explicite (cf. ADR-016) au titre de référence Unicode mondiale dont la reproduction est impossible raisonnablement.
Principe 7 — Verbosité au service de la clarté
Air privilégie la verbosité explicite quand elle clarifie le data-flow, le typage, les invariants, le coût. Pas de magie cachée. Pas de runtime introspectif qui invalide la lecture du code. Pas d’abstractions à plusieurs niveaux quand une seule suffit. Le code Air doit être lisible par un développeur qui découvre le projet, sans connaissance préalable d’un framework « magique ».
Cette préférence se traduit dans la conception des APIs publiques : abonnements explicites plutôt qu’implicites par traçage, conversions explicites plutôt qu’implicites par déréférencement, dépendances passées en argument plutôt qu’injectées en background. Quelques caractères supplémentaires à écrire pour des centaines de fois moins de surprises à comprendre.
Principe 8 — Stabilité contractuelle
Tout symbole air-stable (cf. ADR-012) doit avoir des tests de conformité ABI en plus des tests fonctionnels. Le test charge un binaire de référence compilé contre une version antérieure et vérifie qu’il continue de marcher contre la version courante. Ces tests sont gardés en CI pour toutes les versions supportées.
La stabilité ABI sur dix ans n’est pas un vœu pieux : elle est outillée. Les outils air-abi-check, air-symver, air-deprecation-tracker (introduits dès la phase 0) automatisent la vérification. Un changement qui casserait l’ABI sur un symbole air-stable est rejeté en CI avant même la revue humaine.
Les zones air-internal et air-experimental n’ont pas ces contraintes : elles permettent l’évolution rapide entre release majeures. La discipline ABI est concentrée là où elle est promise.
Principe 9 — Cibles matérielles modestes et diversifiées
Air est développé et validé en continu sur du matériel modeste et diversifié représentatif (Raspberry Pi 4 4 Go et 8 Go en ARM64, Mac mini Intel i5 8 Go et MacBook Pro Intel i7 16 Go en x86_64, en phase initiale, élargi via le catalogue ADR-014). Cette contrainte est volontaire : elle force chaque composant à respecter ses budgets mémoire et CPU, garantit la pertinence multi-architecture (ARM64 + x86_64) dès le jour 1, valide la diversité de GPU et de drivers, et assure que la promesse de longévité du matériel (Charte, principe 4) tient en pratique sur des classes de machines variées.
Si Air ne tourne pas correctement sur les machines de référence, c’est Air qui s’adapte, pas la cible matérielle qui change.
Cette discipline a un effet positif moins évident : elle protège Air contre le syndrome bien connu des logiciels développés exclusivement sur des machines puissantes, qui deviennent imperceptiblement lourds parce que les développeurs ne ressentent pas la lourdeur. Une équipe qui développe sur Raspberry Pi 4 sait immédiatement quand quelque chose ralentit.
Application et évolution
Ces neuf principes sont immuables. Les ADRs et spécifications y sont conformes par construction. Toute évolution requerrait un RFC dédié et un consensus communautaire exceptionnel.
Les principes ne sont pas des slogans : ils sont opérationnels. Ils se traduisent en lints clippy, en checks CI, en revues de code obligatoires, en audits par phase. Un PR qui viole un principe ne passe pas en main, même si le code paraît correct par ailleurs.
Licence du document : MPL 2.0 Statut : Document fondateur immuable.