Spec couche 1 — air-config (compilateur de configuration + artefact Cap’n Proto)
Spécification technique — Version 0.2 (décisions #1–#5 validées BDFL 2026-06-25 ;
grammaire de source + schémas .capnp à concevoir avant implémentation). Couche 1
« Primitives système ». Crate DÉDIÉE (pas dans air-base-lib).
Position et méthode
air-config est le cœur de configuration d’Air : il transforme une source typée
(éditée par l’humain ou une GUI) en un artefact binaire reproductible que le runtime
consomme, et projette vers les fichiers texte attendus par le monde C-Unix. C’est un
composant sécurité-critique (il ingère de la donnée humaine = donnée hostile,
Principe 3) → rigueur couches 0/1 : couverture 100 %, fuzzing obligatoire,
property-based, aucune fn unsafe exposée, arithmétique défensive, nommage ADR-029.
Crate dédiée (décision ADR-040).
air-confign’est pas dansair-base-lib: elle tire Cap’n Proto (cf. §dépendances), etair-base-lib— la crate la plus consommée — doit rester légère. La §3 d’air-base-lib-services(vieuxAirConfigTOML/serde) est superseded par cette spec.
Décisions de socle déjà gravées (NON rouvertes ici)
- ADR-033 — modèle : source typée (canonique) → compilateur validant → artefact binaire ; texte = projection, jamais la forme runtime ; générations + switch atomique + rollback ; backends d’émission.
- ADR-040 — l’artefact est
en Cap’n Proto ; addendum :
capnpcexige le tool C++ au build → politique code généré committé (schémas.capnp+.rsgénéré versionnés ; tool C++ = opération mainteneur, hors CI). - ADR-041 — backends
/etc: projections générées, lecture seule sélective,air-configseul écrivain (swap atomique) ; mutables-runtime →/run;/etc/systemd/systempossédé par le backend systemd.
Vue d’ensemble (le flux)
SOURCE TYPÉE (canonique, versionnable, éditée humain/GUI)
│ air-config-compile : VALIDE en amont (schéma + types + invariants)
│ diagnostics qualité compilateur (ligne/col, message actionnable)
▼
ARTEFACT Cap'n Proto (binaire, reproductible, version + checksum)
├──────────────► RUNTIME Air : lecture ZÉRO-COPIE / mmap (jamais de /etc)
└──► BACKENDS d'émission ──► /etc/* générés (libc/Unix), RO sélectif, /run pour mutables
(sens inverse : IMPORT /etc existant → source, au bootstrap/migration)
Section 1 — Le schéma (Cap’n Proto)
La structure de la configuration est décrite par des schémas .capnp (versionnés,
numérotation de champs = évolution avant/arrière par construction). Le code Rust de
(dé)sérialisation est généré par capnpc et committé (ADR-040 addendum). Chaque
domaine de config (réseau, services, locale…) a son schéma ; un en-tête commun porte
version de schéma + checksum (corruption détectée à l’ouverture, jamais lue en silence).
Section 2 — Le compilateur : air-config-compile
Cœur sécurité-critique (ADR-033 §3). Pipeline pur (sans-IO sur la validation) :
parse(source) → AST typé → valide(schéma + types + invariants) → encode(Cap'n Proto)
- Validation en amont (Principe 4 / « parse, don’t validate ») : schéma, types, invariants métier ; refuse toute entrée invalide avant de produire l’artefact. Le runtime ne voit jamais de config invalide.
- Diagnostics qualité compilateur : ligne/colonne, message actionnable, suggestion
(ADR-019 :
AirErrorriches/contextualisés). - Entrée hostile → fuzzing obligatoire sur le parseur de source + le décodeur d’artefact ; property-based ; 100 % couverture.
DÉCISION #1 (VALIDÉE) — syntaxe de la source = format maison minimal, hand-rolled, zéro-dép, fuzzé. La « source typée » est un texte validé contre le schéma Cap’n Proto, parsé par un parseur maison minimal (pas de
toml/serde→ reste zéro-dép, cohérent ethos ; même discipline que les parseurs DNS/glob déjà hand-rollés + fuzzés). Reste à concevoir : la grammaire concrète de ce format (passe de design dédiée avant implémentation — petit format typé clé/valeur/sections, diagnostics ligne/colonne).
Section 3 — Le lecteur runtime
#![allow(unused)]
fn main() {
/// Ouvre un artefact compilé en lecture ZÉRO-COPIE (mmap via couche 0 `mem`).
/// Vérifie version de schéma + checksum ; corruption → AirError (jamais de lecture muette).
pub struct AirConfig { /* mmap de l'artefact + accès Cap'n Proto */ }
impl AirConfig {
/// # Errors `InvalidData` (version/checksum), `NotFound`.
pub fn open(path: &AirPath) -> AirResult<Self>;
/// Accès typé zéro-copie aux domaines (généré depuis le schéma).
pub fn reader<T: CapnpRoot>(&self) -> AirResult<T::Reader<'_>>;
}
}
- Zéro-copie / mmap (ADR-033 §4, Principe 9) : pas de parse au runtime, lecture par
offsets — gain réel au boot (Pi). Le runtime Air-natif lit l’artefact, jamais
/etc.
Section 4 — Générations, switch atomique, rollback (ADR-033 §6)
- Conserver la dernière génération known-good ; basculer atomiquement
(
air-filesystem::write_atomic) ; retour à la génération précédente (y compris une entrée de secours au boot pour le cas pré-userspace). La validité ne suffit pas — une config valide peut exprimer une mauvaise intention ; le filet anti-brick est le modèle générationnel.
Section 5 — Résolution de chemins (XDG)
Repris de la partie utile de l’ancien air-base-lib §3 (pure construction de chemins) :
AirConfigPaths::for_component(name) → cascade $XDG_CONFIG_HOME / ~/.config/<name> /
$XDG_CONFIG_DIRS / système (système < utilisateur). Lecture effective via air-filesystem.
Section 6 — Backends d’émission /etc + import (ADR-041)
- Émission :
air-configest le seul écrivain des fichiers/etcqu’il possède (générés depuis la source, swap atomique). Backend systemd (possède/etc/systemd/system:*.wants, drop-ins), backend resolv.conf/hosts/nsswitch, etc. Mutables-runtime → symlinks/run. La protection (montage RO sélectif) est une décision couche 5 / intégration OS (horsair-config, qui génère). - Import inverse (bootstrap/migration) : lire
/etcexistant → source Air (avant d’émettre). Périmètre de possession explicite (tout/etcn’appartient pas à Air).
DÉCISION #2 (VALIDÉE) — backends v1, séquencés par risque croissant :
resolv.conf+hosts(simples, quasi-statiques — bons premiers backends) ;- systemd (
/etc/systemd/system:*.wants/drop-ins — activation de services) ;passwd(+shadow) en dernier : c’est la base de comptes, la plus sensible —shadowporte les hashes (secrets → zeroize, jamais loggés, manipulation extra-prudente ; interaction PAM/login). Traité avec le plus grand soin, après que les 3 premiers backends soient éprouvés. Périmètre progressif (ADR-041) ; d’autres backends s’ajouteront ensuite.
Crates / structure — 3 crates (DÉCISION #3 validée)
air-config-schema/ — schémas .capnp + code Rust GÉNÉRÉ (committé, ADR-040) + en-tête version/checksum
air-config-compile/ — le compilateur (parseur source maison → valide → émet Cap'n Proto) [sécurité-critique]
air-config/ — façade : lecteur runtime (mmap zéro-copie), AirConfigPaths, générations/rollback,
ET les backends d'émission /etc + import inverse (modules internes)
Les backends sont des modules d’air-config (pas une 4ᵉ crate) → 3 crates au total.
Dépendances (règle des 80 %, ADR-024)
capnp(runtime, zéro dépendance, pur Rust — vérifié) +capnpc(build-time, code généré committé ADR-040 → tool C++ hors CI ; exception 80 % partagée AirCom).air-base-lib(erreurs/chemins),air-filesystem(lecture/écriture atomique), couche 0mem(mmap). Parseur de source : selon décision ouverte #1 (idéalement zéro-dép). Aucune dépendance systemd (le backend systemd écrit des fichiers).
Stratégie de tests
- Couverture 100 % ; fuzzing : parseur de source (hostile) + décodeur d’artefact +
import
/etc. Property-based : round-trip source→artefact→lecture ; cascade XDG ; générations (rollback rétablit l’exact known-good). Intégration : compile une config réelle, mmap, relit ; émet/etcpuis ré-importe (idempotence) ; corruption d’artefact → erreur (jamais lecture muette). Reproductibilité (ADR-025) : même source → artefact bit-pour-bit (gaterepro) ; encodage Cap’n Proto déterministe.
Décisions ratifiées (BDFL 2026-06-25)
- Syntaxe source = format maison minimal, hand-rolled, zéro-dép, fuzzé (§2 ; grammaire concrète à concevoir avant impl).
- Backends v1 séquencés :
resolv.conf+hosts→ systemd →passwd/shadowen dernier (§6). - 3 crates :
air-config-schema+air-config-compile+air-config(backends = modules d’air-config). - GUI contrainte par schéma : DIFFÉRÉE (un frontend ultérieur ; le cœur validant reste le même).
- Tool C++
capnp: version pinnée (ADR-025), confiné à la régénération mainteneur (mode prébuilt vs source à fixer à l’impl).
Pré-implémentation restante : concevoir la grammaire du format de source (#1) et
définir les schémas .capnp des domaines v1.
Différé
- Frontend GUI ; backends
/etcau-delà du périmètre v1 ; surface ABI C d’air-config(si pertinente) ; intégrationair-network/air-systemd(couche 2) consommant la config.
Licence du document : MPL 2.0
Statut : v0.2 — crate dédiée couche 1, sécurité-critique. Décisions #1–#5 validées.
Restent à concevoir avant implémentation : la grammaire du format de source (maison,
fuzzé) et les schémas .capnp des domaines v1. Bases : ADR-033 / ADR-040 (+ addendum) /
ADR-041.