air-launchd en couche 2 — note de cadrage et découpage
Note de conception. Version 1.0 (2026-08-04). Relevé dans l’arbre le même jour.
Décision cadrée ici (BDFL, 2026-08-04) :
air-launchdest un composant de couche 2, et il est requis dès la v1.0. Cette note dit pourquoi le placement change, ce que la vérification a trouvé — beaucoup plus que prévu —, et en trois temps ce qu’il reste à faire.
1. Pourquoi le placement change
L’hypothèse d’origine, et ce qu’on a appris depuis
Aux premières heures du projet, on espérait — avant d’avoir mesuré la complexité de
systemd — lui faire porter la mise en cage et le sandboxing selon nos critères.
air-launchd était rangé en couche 5, dans la famille « cycle de vie applicatif » de la
macro-architecture, aux côtés d’air-trust, air-appdb et air-update.
Cette hypothèse est tombée. Seul un lanceur à nous peut mettre en œuvre notre niveau de sécurité — l’arbitrage manifeste/octroi d’ADR-150, la cage posée avant de rendre la main, l’enveloppe bornée par l’image. Et s’appuyer sur systemd a, au-delà du sandboxing, trop d’implications structurelles.
Le placement en couche 5 était devenu intenable
ADR-149 D1 pose qu’une couche N n’invoque que les opérations de la couche N-1, sans exception — et a supprimé la section « saut de couches autorisé exceptionnellement » de la macro-architecture.
Or air-launchd a besoin d’air-sandbox et d’air-process (couche 1) et d’AirCom
(couche 2). Depuis la couche 5, ce sont des sauts de trois et quatre étages. En
abolissant l’entorse, on avait rendu impossible le placement du composant qui a le plus besoin
de descendre.
La couche 2 résout cela sans exception, et il y a un précédent exact
air-sshd est déjà un démon de couche 2, et il lie exactement ce dont air-launchd aura
besoin : air-sandbox, air-process, air-filesystem, air-socket, air-log,
air-account, air-keystore — toutes en couche 1, donc N-1. Aucun saut.
Mais l’argument décisif est ailleurs :
La couche déclare ce qu’un composant a le droit de lier, pas qui a le droit de lui parler. Les dépendances AirCom sont des dépendances d’exécution, pas d’édition de liens.
air-wm(couche 3) ne dépend pas de la crateair-launchd— il dialogue avec le démon. Le gatecheck-layersne lit que le graphe de crates ; il ne voit pas, et n’a pas à voir, qui appelle quel service.
La couche 2 est décrite comme « le cœur conceptuel […] les services fondamentaux que toute couche supérieure consomme ». Un lanceur est exactement cela.
Ce que ce déplacement ne résout pas
La formulation d’ADR-149 D1 reste fausse pour la couche 5. ADR-006
(« Profils de déploiement ») pose que le profil air-base a « couche 3 absente ; couche 4
absente ; couche 5 présente » : la couche 5 y repose directement sur la couche 2. « N
n’invoque que N-1 » n’est donc pas vrai littéralement — les étages 3 et 4 sont escamotables.
Le problème est repoussé, pas résolu, et il est aujourd’hui latent : l’arbre ne compte aucune crate en couche 3, 4 ou 5 (2 en couche 0, 33 en couche 1, 45 en couche 2). Le gate n’a jamais eu l’occasion d’y trébucher. À amender au premier vrai composant de couche 5 ; plus en urgence.
2. Ce que la vérification a trouvé — la couture est livrée, pas seulement conçue
ADR-093 §6 annonçait un trait ServiceHost
dont « la v1 est une implémentation systemd, la v2 fournira une implémentation air-launchd
du même trait ».
Ce n’est pas resté sur le papier. La crate air-service existe, en couche 2, et se
décrit elle-même comme « le seam de supervision v1(systemd)→v2(air-launchd) » et « une
fonction de forçage : tout démon Air, pas seulement air-sshd, le réutilise plutôt que de
câbler systemd en dur ». Elle livre :
| Symbole | Rôle |
|---|---|
trait ServiceHost | Le contrat : d’où vient le socket d’écoute, comment notifier l’état, comment recevoir l’ordre d’arrêt |
struct SystemdHost | L’implémentation v1 |
struct ShutdownSignal | La source d’arrêt armée |
parse_listen_fds, parse_notify_socket, format_notify_message, send_notification | Les briques des deux protocoles systemd, en Rust sûr, sans binding libsystemd |
Le contrat tient en trois méthodes : take_listeners() — reprend les sockets hérités, liste
drainée pour interdire la double adoption —, notify(state) et l’armement de l’arrêt.
Et il est prouvé par un test : crates/air-sshd/tests/seam.rs vérifie que SystemdHost
notifie READY contre un socket bouchon.
Conséquence : le premier temps de la roadmap n’est pas de concevoir une abstraction, c’est d’écrire une seconde implémentation d’un trait à trois méthodes déjà éprouvé.
3. Le découpage en trois temps
Temps 1 — le lanceur minimal, superviseur de services
Écrire AirLaunchdHost: ServiceHost à côté de SystemdHost, et le démon qui va avec :
socket-activation, notification de disponibilité, arrêt propre.
Critère de sortie, binaire et mesurable :
air-sshddémarre sousair-launchdau lieu de systemd, sans qu’une ligne d’air-sshdchange. Le trait rend cette affirmation vraie ou fausse, jamais approximative.
Ce temps ne dépend d’aucune décision en attente. C’est ce qui le rend prioritaire : il est
faisable immédiatement, et il transforme air-launchd d’intention en exécutable.
Temps 2 — la mise en cage à l’exec
L’arbitre d’ADR-150 : signature → octroi → enveloppe, puis pose de la cage air-sandbox
avant de rendre la main. C’est ici que se joue « notre niveau de sécurité » — précisément ce
que systemd ne sait pas faire, et la raison pour laquelle l’hypothèse d’origine est tombée.
Bloqué par la question ouverte n° 1 d’ADR-150 : la largeur des enveloppes par défaut.
Le mécanisme de cage existe (ADR-112, fait, en production dans air-sshd) ; ce qui manque
est de savoir quoi poser.
Temps 3 — la supervision réelle : dépendances, ordre, et cgroups
Dépendances entre services, ordre de démarrage, politique de redémarrage — et surtout les cgroups, qui sont ce qui remplace vraiment systemd plutôt que de l’imiter :
- tuer un service de façon fiable (
cgroup.kill) — un service ne s’échappe pas par un doublefork; - compter ce qu’il consomme, exactement ;
- attribuer ses journaux sans qu’il puisse mentir sur son identité ;
- borner les forks (
pids.max), donc la bombe à fourche.
État de la matière — corrigé le 2026-08-15. Cette note affirmait qu’« aucun
AirCgroupManager n’existe ; seul air-sys-syscall connaît le mot ». C’était vrai à la
rédaction, ce ne l’est plus, et une note qui vieillit sans le dire égare autant qu’une note
fausse. crates/air-cgroup existe et porte AirCgroupManager, AirCgroupDelegation et
AirCgroup — delegate, enable_for_children, set_process_limit, set_memory_high,
kill_all, processes — et air-launchd s’en sert (supervise.rs) : cgroup par service,
déménagement du superviseur, pids.max et memory.high posés sans être demandés, mise à
mort de l’arbre par cgroup.kill.
La prévision de placement, elle, s’est vérifiée : ce n’est pas un descellement de couche 0
— les cgroups se pilotent par le système de fichiers (/sys/fs/cgroup), donc par le manager de
fichiers. C’était bien un Manager de couche 1 à écrire, pas des syscalls à envelopper.
Ce qui reste du temps 3 après ce constat n’est donc pas le bornage, mais les dépendances, l’ordre, le sens de « prêt » et l’arrêt ordonné — ADR-161.
4. Ce que cette note ne tranche pas
- La largeur des enveloppes par défaut — bloque le temps 2, et reste la question ouverte n° 1 d’ADR-150.
- Le registre de noms et la distribution de la capability initiale par le lanceur
(ADR-010, POLA strict). C’est ce qui rendrait le rendez-vous AirCom trivial pour une
application ; cf.
notes/aircom-surface-rust-sure-conception-fr.md. - Le sort de systemd après le temps 1. Deux implémentations du même trait peuvent
coexister longtemps ; décider si
SystemdHostreste, et jusqu’à quand, n’est pas fait ici. - La formulation d’ADR-149 D1 pour la couche 5, repoussée ci-dessus.
Licence du document : MPL 2.0 Statut : note de conception. Cadre un découpage ; les décisions engageantes vivent dans les ADR.