Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

EXCEPTIONS.md — Exceptions explicites à la règle des 80 %

Document de référence — Version 0.1

Rôle de ce document

Le Principe d’ingénierie 6 (cf. docs/principes-ingenierie/principes-ingenierie-fr.md) et l’ADR-024 (cf. docs/adrs/ADR-024-workflow-dependances-fr.md) imposent une règle stricte : une dépendance externe n’est admise que si au moins 80 % du code de la crate candidate est effectivement utilisé par Air. Sous ce seuil, la dépendance est refusée et le code nécessaire est intégré en sous-module Air.

Le ratio se mesure sur le code de la crate, pas sur la « surface d’API utile ». Une crate qui n’expose qu’une seule API publique mais qui émet pour chaque usage des dizaines de méthodes et de traits dérivés peut très bien échouer au critère même si « tout ce qu’on appelle » est utilisé : ce qui compte, c’est le volume de code embarqué dans nos binaires.

Certaines dépendances sont néanmoins structurantes : leur réimplémentation serait disproportionnée, leur adoption massive dans l’écosystème en fait des références de fait, leurs garanties (sécurité, maintenance, audit) seraient impossibles à reproduire en interne. Pour ces cas, le Principe 6 prévoit une exception explicitement nommée, jamais implicite. Ce fichier consigne ces exceptions.

Toute exception doit :

  1. Être motivée par un besoin transverse à Air (pas un usage ponctuel d’une seule crate Air).
  2. Lister les critères de dérogation (réimplémentation déraisonnable, adoption massive, garanties non reproductibles, etc.).
  3. Préciser les mitigations (audit de sécurité, pin de version, suivi).
  4. Référencer l’ADR ou la décision qui a tranché.

Exceptions actives

bitflags

ChampValeur
Cratebitflags
Version pinnée=2.11.1 (pin strict couches 0/1, cf. Cargo.toml racine)
LicenceApache-2.0 OR MIT (liste blanche, cf. CLAUDE.md)
Dépendances transitivesaucune en v2
Adoptée parair-sys-types (et toutes les crates couche 0 ultérieures)
Tranchée parsession 2026-05-22 (suite : clone3/waitid) — cf. docs/JOURNAL.md

Motif. Crate macro structurante : son macro bitflags! génère pour chaque type de drapeau un ensemble complet de traits (BitOr, BitAnd, BitXor, Not, Sub, Extend, FromIterator, Debug, Hash, constructeurs empty/from_bits/from_bits_truncate/from_bits_retain, itérateurs, etc.). Mesuré sur le code émis, Air n’en utilise qu’une fraction par type (typiquement bits, contains, from_bits_truncate, empty, plus un ou deux opérateurs binaires) — la crate échoue donc strictement au seuil des 80 % du Principe 6.

Pourquoi l’exception est justifiée.

  1. Volume structurant. Les specs docs/specs/layer-0/family-*.md identifient plus de 30 types bitflags dans la seule couche 0 (CloneFlags, WaitOptions, OpenFlags, MapFlags, ProtectionFlags, MessageFlags, RenameFlags, StatxMask, StatxFlags, SignalFdFlags, SeccompFilterFlags, LandlockAccessFs, TimerFdFlags, MlockFlags, AcceptFlags, …). Réimplémenter manuellement les traits associés à chacun serait plusieurs centaines de lignes de boilerplate fragile, dupliqué.

  2. Reproduction déraisonnable. Une réimplémentation interne couvrirait à peu près le même code émis (les traits sont mécaniques), pour une moindre qualité de tests et de revue. Le bénéfice « moins de code dans la crate externe » est annulé par le coût « plus de code maison ».

  3. Adoption massive. bitflags est de facto le standard de l’écosystème Rust pour ce besoin. Les autres briques bas niveau de l’écosystème (nix, libc, rustix, …) l’utilisent déjà — ce sont des points de comparaison externes, pas des dépendances d’Air : la couche 0 d’Air appelle les syscalls directement via core::arch::asm! et n’utilise pas rustix. La maintenance de bitflags est portée par rust-lang/bitflags.

  4. Zéro dépendance transitive en v2 : la surface d’exposition externe se limite à bitflags lui-même.

Mitigations.

  • Pin strict =2.11.1 dans [workspace.dependencies]. Deux protections complémentaires : (a) la reproductibilité du build est assurée par le Cargo.lock committé (ADR-025) ; (b) la reproductibilité de la résolution est ajoutée par le pin strict — si jamais le lock est supprimé ou qu’une re-résolution est forcée (ajout d’une autre dép qui contraint le solver, cargo generate-lockfile), une borne ^2.4 pourrait glisser vers la plus récente 2.x compatible ; =2.11.1 ne glisse pas. Chaque montée de version reste ainsi une décision explicite tracée dans la diff de Cargo.toml, revue en PR.
  • Pas de politique transversale dans cette PR : la question « =strict partout vs ^caret au-delà de la couche 1 » mérite un ADR ou un DEPENDENCY_POLICY.md dédié, à instruire séparément.
  • cargo audit dans la chaîne CI (à activer dès qu’elle existe).
  • cargo deny vérifie la licence et l’absence de dépendances transitives non autorisées (à activer dès qu’elle existe).
  • Audit ponctuel à chaque montée de version majeure ; consigné dans le JOURNAL et, si non trivial, dans un ADR.

Ce que cette exception n’autorise pas.

  • Ajouter d’autres crates similaires sans nouvel examen (chaque crate macro est jugée sur ses propres mérites).
  • Substituer bitflags à un besoin couche 1+ sans réévaluer le contexte (les couches supérieures peuvent avoir des contraintes différentes).
  • Faire évoluer l’usage au-delà de la macro bitflags! et de ses traits générés (par exemple, vers des fonctionnalités exotiques) sans amender cette entrée.

icu4x (composants icu_*)

ChampValeur
Cratesicu_normalizer, icu_casemap, icu_segmenter, icu_collator, icu_locale_core, icu_calendar (composants à la carte)
Versions pinnées=2.2.0 (sauf icu_calendar =2.2.1), default-features = false + feature compiled_data (+ auto pour icu_segmenter) — réglage gravé ADR-059 §4 (coupe transitivement std), cf. [workspace.dependencies] racine
LicenceUnicode-3.0 (ajoutée à la liste blanche deny.toml ; icu4x 2.x a quitté Unicode-DFS-2016)
Adoptée parair-base-lib (couche 1) ; surface Unicode/i18n du cœur
Tranchée parADR-016 (i18n) ; câblage rapport 073 (couche 1, Passe 2) ; ADR-059 (stratégie Option V : Air utilise icu4x, pas de réimplémentation)
Nature techniqueRust pur no_std, PAS ICU4C (réimplémentation from-scratch d’unicode-org, zéro binding C, zéro FFI). Repose sur core + alloc (alloue pour les sorties ; zéro IO/syscall/horloge/thread ; allocateur cible = air-alloc). Runtime = tables const baked (compiled_data, en .rodata) + fonctions pures ; std mécaniquement exclu sur la cible (build-std core+alloc). Vérifié empiriquement : la fermeture (air-base-lib + icu + socle) compile pour x86_64/aarch64-unknown-linux-air (--release + default-features=false + build-std=core,alloc, ADR-059 ; roadmap §2.A.2). Conforme « zéro surface C/C++ » (gate check-c-surface).

Motif. icu4x est la base Unicode/i18n d’Air (ADR-016). Chaque composant expose une API très large (normalisation, casing, segmentation, collation, calendriers, locales) dont air-base-lib n’utilise qu’une fraction (estimée 10–20 % par composant, cf. crates/air-base-lib/DEPENDENCIES.md). Mesuré sur le code embarqué (données Unicode baked-in comprises), l’ensemble échoue donc strictement au seuil des 80 % du Principe 6 — d’où l’exception structurante.

Pourquoi l’exception est justifiée.

  1. Réimplémentation déraisonnable et dangereuse. Réécrire une normalisation Unicode (UAX #15), une segmentation par graphèmes/mots/phrases (UAX #29), une collation (UTS #10) ou les arithmétiques de calendriers serait des milliers de lignes, avec un risque élevé de bugs de sécurité (confusion d’encodage, troncatures) — précisément ce que le Principe 3 cherche à éviter.
  2. Données Unicode à jour. icu4x embarque les tables Unicode officielles, versionnées et maintenues par le consortium ICU/Unicode — non reproductible en interne sans dette de maintenance permanente.
  3. Modularité native. icu4x est conçu en composants tirables à la carte : on n’embarque que les domaines du contrat (pas de runtime data provider grâce à compiled_data), ce qui borne la surface réelle.
  4. Frontière confinée. L’usage est isolé dans air-base-lib (couche 1) ; le formatage culturel (couche 2) viendra plus tard, sans élargir cette entrée.

Mitigations.

  • Pins stricts (=2.2.0 / =2.2.1) dans [workspace.dependencies] : la montée de version reste une décision explicite tracée en PR (repro ADR-025).
  • compiled_data (données baked-in) : aucun chargement runtime, surface d’attaque réduite, build reproductible.
  • cargo deny vérifie la licence Unicode-3.0 (liste blanche) et les transitives ; cargo audit couvre les avis de sécurité.
  • % d’API par composant documenté et daté dans crates/air-base-lib/DEPENDENCIES.md ; ré-audit à chaque montée majeure.
  • Composant écarté (icu_time) : retiré faute d’usage réel (conversion CLOCK_REALTIME → ISO dérivée via icu_calendar::RataDie).

Ce que cette exception n’autorise pas.

  • Ajouter d’autres composants icu_* sans mesurer leur usage réel et amender DEPENDENCIES.md + cette entrée.
  • Activer un data provider runtime ou des features élargissant la surface sans réévaluation (rester sur compiled_data).
  • Étendre l’usage d’icu4x à d’autres crates sans réexaminer le contexte de couche.

air-crypto — crypto auditée (RustCrypto, dalek, zeroize)

ChampValeur
Cratessha2, sha3, hmac, aes-gcm, chacha20poly1305, hkdf, argon2, zeroize (RustCrypto) ; ed25519-dalek, x25519-dalek (dalek) ; subtle (transitif, constant-time)
Versions pinnéescf. [workspace.dependencies] racine + crates/air-crypto/DEPENDENCIES.md. no_std-strict (roadmap §2.E.2) : default-features = false sur sha2/sha3/aes-gcm/chacha20poly1305/argon2/ed25519-dalek (coupe la fuite std transitive ; features réactivées = strict nécessaire, cf. DEPENDENCIES.md)
LicencesMIT OR Apache-2.0 (RustCrypto, zeroize) ; BSD-3-Clause (dalek) — toutes en liste blanche deny.toml
Adoptée parair-crypto (couche 1)
Tranchée parADR-034 (discipline crypto) ; câblage prompt 086 (Passe 2) ; no_std-strict roadmap §2.E.2

Motif. « On ne réimplémente JAMAIS de crypto. » C’est la crate où la discipline « minimiser les deps » est délibérément renversée (cf. spec docs/specs/layer-1/air-crypto.md) : rouler sa propre crypto est dangereux (attaques temporelles, bugs subtils, zéro audit). La sécurité vient de l’adoption massive et auditée. Chaque crate n’expose qu’une fraction de son API générique utilisée par Air (~10–40 %, cf. DEPENDENCIES.md) — elles échouent donc à la règle des 80 %, d’où l’exception structurante (analogie icu4x, ADR-016).

Pourquoi justifiée.

  1. Réimplémentation déraisonnable et dangereuse — AES-GCM, ChaCha20-Poly1305, Ed25519, X25519, Argon2, SHA-2/3 sont des primitives où la moindre erreur (non constant-time, débordement) est une faille. RustCrypto/dalek sont audités, constant-time, et corrigent vite (fast-lane ADR-034).
  2. Pur Rust, zéro C/C++ — toutes ces crates respectent la politique zéro-C (#81) : cargo xtask check-c-surface et le ban cc/*-sys restent verts. (BLAKE3 a été différé précisément parce que blake3 tire cc — cf. DEPENDENCIES.md et la doc du module hash.) (Réserve aarch64 (roadmap §2.E.2) : la fermeture no_std compile sur x86_64-unknown-linux-air sans aucun libc, mais sur aarch64 le crate cpufeatures — dép non-optionnelle pour la détection ARMv8 — lie libc (getauxval), qui n’a pas de profil env=air. Manque de support cible / surface C à lever par un chemin libc-free air_auxval(AT_HWCAP) — chantier dédié, cf. DEPENDENCIES.md §« Fermeture no_std-strict ».)
  3. API anti-mésusage — Air enrobe ces crates d’une surface qui rend les erreurs courantes difficiles (clés zeroize, AEAD à nonce géré, vérifs constant-time), sans toucher au cœur crypto.

Additifs planifiés (descellement couche-1-v1.x, RFC — support air-tls). Pour l’interop WebPKI de la contingence air-tls maison (ADR-042 §Contingence ; docs/specs/layer-2/air-tls.md ; instruits en docs/specs/layer-1/air-crypto.md §« Additifs planifiés ») : p256/p384 (ECDSA + ECDH NIST, RFC 6979 déterministe), rsa (RSA-PSS vérification seule — aucune opération privée RSA → hors RUSTSEC-2023-0071 « Marvin »), ml-kem (FIPS 203, hybride X25519MLKEM768 only), et la variante AES-128-GCM de l’aes-gcm déjà présent. Toutes pur Rust, zéro-C (check-c-surface vert — à re-vérifier au bump, réserve aarch64/cpufeatures ci-dessus). Chaque entrée : RFC de descellement (modèle ADR-065/066/067) + KAT-gate (ADR-034)

  • ajout à DEPENDENCIES.md. Non actives tant que le descellement n’est pas ratifié.

Additifs air-quic/air-ssh (ADR-081). Révélés par les specs QUIC (header protection) et SSH (signatures/cipher) : aes/chacha20/poly1305déjà transitifs (via aes-gcm/chacha20poly1305), promus dépendances directes pour un module air_crypto::lowlevel (bloc AES single-block, keystream ChaCha20, Poly1305 one-shot — primitives nues opt-in expert-only, la façade AEAD restant le défaut) ; rsa (déjà ci-dessus) étendu à RSASSA-PKCS#1 v1.5 vérification (hors Marvin, opération publique). Kex PQ SSH = ml-kem réutilisé (mlkem768x25519) ; sntrup761 différé. Pur Rust, zéro-C, ratification BDFL + KAT-gate.

Additifs air-wireguard/air-mail + AEAD nonce explicite (ADR-082). (1) AEAD à nonce explicite dans lowlevel (AirAeadExplicitNonce, backends aes-gcm/ chacha20poly1305 déjà présents — seule l’API à nonce fourni s’ajoute) : requis par record/packet TLS/QUIC/SSH/WireGuard (nonce déterministe IV⊕compteur, ≠ nonce aléatoire de la façade). (2) blake2 (nouveau, RustCrypto, pur Rust zéro-C ≠ blake3-qui-tire-cc) : BLAKE2s hash/MAC/HMAC pour le handshake Noise de WireGuard. (3) pbkdf2 (nouveau, RustCrypto) : SCRAM-SHA-256 de air-mail. Ratification BDFL + KAT-gate.

Mitigations.

  • Pins stricts (versions =) ; bumps KAT-gated (ADR-034) ; cargo audit (fast-lane sécurité) + cargo deny (licences permissives) en CI.
  • Vecteurs officiels (KAT) RFC/NIST en tests + property-based + fuzzing.
  • Discipline régie par ADR-034 (veille upstream, exemption de vendoring).

Ce que cette exception n’autorise pas.

  • Réintroduire une surface C/C++ (p. ex. ring, aws-lc, blake3 avec cc) — interdit par la politique #81 ; nécessiterait sa propre exception nommée + plan de sortie.
  • Écrire de la crypto maison ou utiliser un PRNG userspace pour l’aléa (AirRandom reste le CSPRNG kernel).
  • Ajouter d’autres crates crypto sans audit ADR-034 + entrée ici.

capnp — runtime Cap’n Proto (partagé air-config ↔ AirCom)

ChampValeur
Cratecapnp
Version pinnée=0.26.0 (pin strict couche 1, cf. Cargo.toml racine)
LicenceMIT (liste blanche, cf. CLAUDE.md)
Dépendances transitivesaucune (features quickcheck/embedded-io non activées ; embedded-io?/… non tirée)
Adoptée parair-config-schema, air-config-compile, air-config (lecture zéro-copie de l’artefact mappé) et AirCom (air-com-schema : schéma v1 du wire format ; air-com-proto : Codec du control plane), ADR-001
Tranchée parADR-040 (2026-06-25, BDFL)

Motif. L’artefact binaire de configuration d’Air est encodé en Cap’n Proto (ADR-040), choisi pour la lecture zéro-copie/mmap (budget runtime au boot, Principe 9) et la cohérence projet : AirCom (ADR-001) s’engage déjà sur Cap’n Proto. Air n’utilise qu’une fraction de l’API du crate capnp (encodage + réflexion dynamique de schéma) — la crate échoue donc au seuil des 80 % du Principe 6, d’où l’exception structurante.

Pourquoi l’exception est justifiée.

  1. Audit mutualisé (décisif). La dépendance Cap’n Proto est de toute façon justifiée pour AirCom ; air-config la réutilise sans coût d’exception supplémentaire — un seul langage de schéma, une seule chaîne de codegen, une seule dépendance à auditer sur tout le stack (ADR-040 §Conséquences).
  2. Pur Rust, zéro C/C++ au build. Le crate runtime capnp a zéro dépendance transitive et n’introduit aucune surface C/C++ : cargo xtask check-c-surface et le ban cc/*-sys restent verts. Le tool C++ capnp (front-end de schéma) est confiné à la régénération mainteneur du code généré committé (ADR-040 addendum), hors build/CI.
  3. Réimplémentation déraisonnable. Un format schema-first zéro-copie à évolution intégrée n’est pas reproductible en interne (horizon 20 ans).

Mitigations.

  • Pin strict =0.26.0 ; code généré committé → build pur cargo, reproductible (ADR-025) ; tool C++ capnp version pinnée 1.1.0, hors CI.
  • cargo deny (licence MIT permissive) + cargo audit en CI.

Ce que cette exception n’autorise pas.

  • Réintroduire une surface C/C++ (le tool C++ reste hors build ; aucun capnp-sys/cc).
  • Tirer d’autres crates de l’écosystème Cap’n Proto sans entrée ici.

libm — math flottante no_std (air-libm)

ChampValeur
Cratelibm (rust-lang)
Version pinnée=0.2.16 (pin strict couche 1, cf. Cargo.toml racine)
LicenceMIT (liste blanche deny.toml ; l’amont rust-lang/compiler-builtins est dual MIT OR Apache-2.0)
Dépendances transitivesaucune (pur Rust no_std)
Adoptée parair-libm (couche 1) — surface math no_std d’Air
Tranchée parADR-057 (2026-07-01, par délégation d’autonomie BDFL ; companion ADR-053/ADR-024)

Motif. L’audit icu4x (docs/notes/audit-icu4x-rust-pur-fr.md, §3/§8) a établi que le seul primitif système qui manque à la réimplémentation i18n / libc d’Air est une bibliothèque mathématique flottante no_std — un libm. Deux consommateurs incontournables en dépendent : calendrical_calculations (math calendaire, via icu_calendarnon contournable) et le modèle LSTM d’icu_segmenter, tous deux par la façade core_maths sur libm. Air vendore la crate libm (rust-lang) et l’expose via la crate couche 1 air-libm (pub use libm::*;). Le % d’API « consommé » est aujourd’hui nul (aucun consommateur encore — c’est une fondation posée pour la libc/i18n future, ADR-053) ; la crate échoue donc trivialement au seuil des 80 % du Principe 6, d’où l’exception structurante (modèle icu4x, ADR-016).

Pourquoi l’exception est justifiée.

  1. Réimplémentation déraisonnable et risquée. Réécrire un libm correct from scratch serait des dizaines de fonctions transcendantes à précision ULP, avec cas limites NaN/inf/dénormaux — un risque de bugs numériques précisément là où Air ne peut pas se permettre l’à-peu-près. Le bénéfice « moins de code externe » serait annulé par le coût « plus de code maison moins testé ».
  2. Qualité std, maintenu par le projet Rust. libm est fusionné dans compiler-builtins : c’est le libm que la std Rust utilise elle-même sur les cibles no_std/wasm. Sa correction est éprouvée et continûment testée par l’amont rust-lang.
  3. Pur Rust no_std, zéro dépendance transitive. Portage de fdlibm en Rust pur, sans aucune dép runtime ni surface C/C++ (cargo xtask check-c-surface reste vert ; aucun *-sys, aucun cc). Compatible avec la cible *-linux-air (prouvé par crate-sonde, ADR-057).

Mitigations.

  • Pin strict =0.2.16 dans [workspace.dependencies] : la montée de version reste une décision explicite tracée en PR (repro ADR-025).
  • cargo deny vérifie la licence MIT (liste blanche) et l’absence de transitives ; cargo audit couvre les avis de sécurité.
  • Frontière confinée dans air-libm (couche 1) ; aucune autre crate ne tire libm directement.
  • % d’API et audit consignés/datés dans crates/air-libm/DEPENDENCIES.md ; ré-audit à chaque montée.

Ce que cette exception n’autorise pas.

  • Tirer libm directement depuis une autre crate sans passer par air-libm (frontière unique).
  • Substituer une autre crate math (core_maths, micromath, …) sans nouvelle entrée ici.
  • Faire évoluer air-libm au-delà du ré-export (logique math propre) sans réévaluer le contexte.

syn / quote / proc-macro2 — outillage proc-macro (air-object-macros, politique proc-macro d’Air)

ChampValeur
Cratessyn, quote, proc-macro2 (+ transitif unicode-ident)
Versions pinnéessyn =2.0.117 (default-features = false, features derive/parsing/printing/clone-impls/proc-macro), quote =1.0.45, proc-macro2 =1.0.106 — cf. [workspace.dependencies] racine
LicencesMIT OR Apache-2.0 (les trois) ; unicode-ident (MIT OR Apache-2.0) AND Unicode-DFS-2016 — toutes en liste blanche deny.toml
Adoptées parair-object-macros (couche 2, premier crate proc-macro d’Air) — l’attribut #[air_class] (spec docs/specs/layer-2/air-object.md §4 ; ADR-002)
Tranchée parspec air-object.md §4 « Décisions ratifiées » n°5 (BDFL 2026-06-25) : exception 80 % proc-macro acceptée → « fixe la politique proc-macro d’Air »
Nature techniquePur Rust, ZÉRO surface C/C++ (cargo xtask check-c-surface reste vert ; aucun *-sys, aucun cc). Build-time only : une proc-macro ([lib] proc-macro = true) compile pour l’HÔTE et est exécutée par rustc au build des consommateurs — elle n’est JAMAIS liée dans un artefact cible (rien n’entre dans libair-object.so ni dans aucune .so/binaire livré).

Motif. Une libc/un modèle d’objet à surface stable a besoin de génération de code à la compilation (#[air_class] synthétise la table de descripteurs const d’AO.3 — « zéro glue par classe »). Le seul écosystème crédible pour parser/émettre du Rust dans une proc-macro est le trio syn (parseur), quote (émetteur), proc-macro2 (couche TokenStream neutre). Chaque crate expose une API très large (syn couvre toute la grammaire Rust) dont air-object-macros n’utilise qu’une fraction (parsing de DeriveInput/attribut, quote!) : mesuré sur le code embarqué, l’ensemble échoue au seuil des 80 % du Principe 6 — d’où l’exception structurante (modèle bitflags/icu4x).

Pourquoi l’exception est justifiée.

  1. Réimplémentation déraisonnable. Réécrire un parseur de la grammaire Rust (suivant l’évolution du langage) serait des dizaines de milliers de lignes, pour une moindre qualité — précisément le cas où le Principe 6 prévoit l’exception nommée.
  2. Adoption massive et de facto. syn/quote/proc-macro2 (rust-lang / dtolnay) sont l’outillage standard de tout l’écosystème proc-macro Rust (serde, thiserror, clap… et la plupart des *-derive). Maintenance et audit portés en amont.
  3. Rien dans l’artefact livré (décisif). Contrairement aux deps runtime, une proc-macro ne survit pas au build : rustc l’exécute puis la jette. La libair-object.so cible tier-1 ne contient aucun octet de syn/quote — le critère « code embarqué dans nos binaires » vaut zéro ici. C’est l’exception la moins coûteuse du registre.
  4. Zéro surface C/C++ (check-c-surface vert) et zéro fork (ADR-090) : une proc-macro compile pour l’hôte, elle échappe à la fermeture no_std/*-linux-air de la cible.

Mitigations.

  • Pins stricts (=) dans [workspace.dependencies], alignés sur le Cargo.lock déjà résolu : toute montée reste une décision explicite tracée en PR (repro ADR-025).
  • default-features = false sur syn (features tirées à la carte : pas de full, pas d’extra-traits) : surface réduite au strict nécessaire.
  • cargo deny (licences permissives) + cargo audit (avis de sécurité) en CI ; cargo machete vérifie que les trois deps sont réellement utilisées.
  • % d'API et audit consignés/datés dans crates/air-object-macros/DEPENDENCIES.md.
  • Frontière confinée : seul air-object-macros tire ces crates ; toute future crate *-macros réutilise cette politique (ci-nommée) sans nouvelle exception, tant qu’elle reste build-time only et pur Rust.

Ce que cette exception n’autorise pas.

  • Tirer syn/quote/proc-macro2 depuis une crate runtime (liée dans un artefact) — l’exception est bornée au build-time only ([lib] proc-macro = true).
  • Ajouter une feature élargissant la surface (syn/full, extra-traits…) sans amender cette entrée + DEPENDENCIES.md.
  • Réintroduire une surface C/C++ dans la chaîne proc-macro (aucun *-sys/cc).

syscall générique (raw_syscall) — escape hatch, entorse ADR-021 + « libc binde couche 1 » — À RETIRER

ChampValeur
NatureFonction Rust air_sys_syscall::raw_syscall::raw_syscall(number, a1..a6) (asm brut 2 arches, couche 0) + shim libc cible-only air_libc_capi::syscall qui la binde directement.
Politiques dérogées(1) ADR-021 conv. 3 — « pas de wrapper générique pour syscalls multiplexés ; chaque opération = une fonction dédiée typée ». (2) « la libc binde la couche 1, jamais la couche 0 » — le shim syscall est le seul point de la libc tapant la couche 0.
Adoptée parair-libc-capi (module syscall, #[cfg(target_vendor = "air")]) sur air-sys-syscall (dép cible-only).
Tranchée parDécision BDFL — 2026-07-09 (M5, Lot D) : « implémenter syscall comme std l’attend (générique, tape la couche 0 s’il faut), mais noter l’exception et prendre contact avec l’équipe Rust pour la solutionner — à retirer le plus vite possible ».
Statut⚠️ TEMPORAIRE — à retirer.

Motif (l’impossibilité). La libc C-ABI doit fournir long syscall(long number, ...) : le pal unix de Rust std l’appelle en direct pour deux services sans fonction libc typée — futex (aucune fonction libc n’a jamais existé ; base des Mutex/Condvar/Once/park de std sur Linux) et gettid (pas de wrapper garanti avant glibc 2.30). syscall(2) est par nature une trappe non typée : on ne peut pas la rendre typée sans la dénaturer, ni la router proprement (les flags d’opération futex de std rendent tout routage fragile).

Pourquoi c’est borné.

  1. Un seul point, cible-only. raw_syscall n’a qu’un consommateur (le shim syscall), gaté target_vendor = "air" ; il n’existe pas dans l’artefact hôte.
  2. Aucun code Air ne l’utilise. Tout le reste d’Air passe par des wrappers typés (futex_wait, gettid, …) ; raw_syscall est exclusivement la trappe pour le C std qui contourne les API typées.
  3. Périmètre M5 quasi nul au runtime. hello-std étant mono-thread, futex n’est jamais appelé (contention uniquement) — le symbole doit juste exister pour lier.

Plan de retrait (action item amont — équipe Rust). Faire que le pal unix de std n’ait pas besoin d’un syscall générique sur *-linux-air : router futex/gettid vers des fonctions typées Air, ou fournir un pal Air dédié. Dès que l’amont est résolu, raw_syscall et le shim syscall disparaissent (et cette exception avec).

Shims C va_arg variadiques (printf… + open/openat/fcntl) — exception nommée à « zéro C »

ChampValeur
NatureCode C interne (≈ 190 + ≈ 90 lignes), PAS une dépendance Cargo. Fichiers crates/air-libc-c/csrc/printf_shim.c (+ header privé air_fmt_glue.h) et crates/air-libc-c/csrc/fileio_shim.c, compilés par crates/air-libc-c/build.rs et liés dans libair_c.so.
Politique dérogée« zéro surface C/C++ » (audit 082 / prompt 083 ; gate check-c-surface, ban cc/*-sys de deny.toml). C’est une exception à la politique zéro-C, distincte de la règle des 80 % — consignée ici, le registre des exceptions nommées.
Adoptée parair-libc-c (artefact libair_c.so, host-only).
Tranchée parDécision BDFL — roadmap libc §6 : M1-d « frontière variadique printf = option (A) » ; M2 l’étend à open/openat/fcntl (mêmes contraintes : fonctions variadiques, mode/arg conditionnel — cf. roadmap §6 M2 « open… variadique → shim C fait le va_arg »).

Motif (l’impossibilité technique). Une libc C-ABI doit fournir printf/ fprintf/snprintf/… et open/openat/fcntl — des fonctions C variadiques (const char *fmt, ... ; int flags, ... /* mode_t */). Or Rust-stable ne peut pas définir de fonction variadique : extern "C" fn f(…, ...) exige la feature c_variadic, nightly, et la libc d’Air reste sur stable (ADR-025, barrier, couverture 100 %). Il faut donc un point de contact C pour le seul mécanisme que Rust-stable ne sait pas exprimer : le va_arg.

Pourquoi l’exception est justifiée (périmètre minimal, zéro surface d’attaque).

  1. Le shim ne fait QUE va_arg + délégation. Il ne parse rien : aucune boucle de format, aucun calcul de largeur/précision, aucune interprétation de %. Pour chaque fonction, il (a) demande le plan des types d’args à la glue Rust (air_fmt_arg_kinds), (b) fait le va_arg(ap, <type>) mécanique dicté par le plan (en respectant les promotions C), (c) re-délègue au moteur de rendu Rust (air_fmt_render_buf/_stream). C’est trivialement auditable.
  2. La surface d’attaque N°1 reste en Rust fuzzé. La chaîne de format — la surface « format-string » d’une libc — est parsée et rendue entièrement par air-libc-fmt (Rust pur, sans unsafe, sans allocation, fuzzé : fuzz_libc_format), via la glue mesurée air-libc-printf (fuzz_libc_printf). Le plan et le rendu passent par le même parseur (air_libc_fmt::format) ⇒ ils ne peuvent pas diverger ; le C ne voit jamais un %.
  3. %n rejeté de bout en bout. Le moteur rend %n littéralement et ne tire aucun argument ⇒ le plan n’émet aucun kind, le shim ne fait aucun va_arg, jamais d’écriture-arrière (vecteur format-string classique neutralisé).
  4. Aucune dépendance Cargo, aucune surface *-sys. build.rs invoque cc/ $CC directement (std::process::Command), jamais la crate cc (bannie). cargo tree, check-c-surface et cargo deny sont inchangés/verts (le compilateur C est un outil hôte, comme cbindgen/Doxygen/capnp).

Mitigations.

  • Périmètre gelé : les shims n’implémentent que (a) printf/fprintf/sprintf/ snprintf + variantes v* et (b) open/openat/fcntl — dans les deux cas le seul va_arg mécanique + délégation à un point d’entrée Rust non-variadique (air_fmt_* / air_libc_{open,openat,fcntl}). fileio_shim.c ne fait aucun syscall, aucune interprétation de drapeau (la décision « O_CREAT ⇒ lire mode » est un simple test de bit) ; toute la sémantique (openat2, CLOEXEC, EINTR) vit en Rust. Tout autre élargissement exige d’amender cette entrée.
  • Garde ABI de la représentation d’argument partagée (FmtArgValue) : verrou _Static_assert côté C et test size_of/offset_of côté Rust (une dérive de layout casse la compilation — défense en profondeur, sécurité #1).
  • Reproductibilité (ADR-025) : mêmes sources + même $CC ⇒ même objet ; options figées dans build.rs.
  • Couverture : la glue Rust (air-libc-printf) est mesurée (viser 100 %) ; le shim .c (insondable par llvm-cov, comme les shims #[no_mangle]) est couvert par le harnais hello-world C réel (air-libc-c/tests/hello_world.rs) + les tests de conformité ABI.
  • Preuve d’exécution : nm -D libair_c.so montre printf… ; le hello-world C imprime Hello, Air 42.

Portée. Host maintenant (cc/clang de l’hôte de dev/CI). Le portage on-target (*-linux-air, toolchain clang-air) est hors périmètre M1-d — la crate d’artefact est host-only. Le shim y sera recompilé par le toolchain cible le moment venu (aucun changement de conception : c’est le même va_arg).

Ce que cette exception n’autorise pas.

  • Mettre quelque logique de format que ce soit en C (parsing, largeur/précision, %n$ positionnels, locale) — tout cela reste en Rust.
  • Réintroduire une dépendance Cargo C (cc, *-sys) — l’invocation directe de cc/$CC est le seul vecteur autorisé.
  • Étendre le C au-delà des familles printf et open/openat/fcntl (p. ex. scanf/syscall/ioctl variadiques) sans une nouvelle décision BDFL + mise à jour de cette entrée.

Licence du document : MPL 2.0 Statut : registre vivant des exceptions explicites au Principe d’ingénierie 6.