ADR-157 — Le lanceur supervise ce qu’il trouve : plusieurs services, déclarés par leur bundle
Statut : Accepté (2026-08-13, BDFL). Réalise le temps 3a de la spec
air-launchd et débloque l’hébergement du registre AirCom.
Prolonge ADR-010 (disposition des bundles) et
ADR-150 (manifeste, octroi, arbitrage).
Catégorie : Architecture système. Décide comment un système Air sait quoi démarrer.
Promu depuis l’instruction
instruction-multi-service-lanceur-fr.md.
Contexte
air-launchd supervise un seul service par invocation : parse_arguments rend un
ServiceSpec, et le programme lui vient de la ligne de commande. C’est le dernier reste
nommé du temps 3 de sa spec — « dépendances, ordre, redémarrage » — qu’aucun ADR ne
décidait.
Ce n’est pas seulement une limite : cela bloque l’étape 2b-ii de la campagne des managers (ADR-149 D8). Héberger le registre AirCom dans un lanceur mono-service donnerait un registre à client unique, dont la propriété essentielle — mettre deux services en relation — ne serait jamais exerçable. Ce serait une permission que rien n’applique, ce que l’option B d’ADR-150 refuse.
Le découpage, et pourquoi il n’est pas de commodité
2b-ii n’a pas besoin des dépendances. Il lui faut plusieurs services vivants dans un même lanceur, rien de plus. Les deux moitiés répondent donc à des questions différentes :
- 3a — N services supervisés en parallèle, sans relation entre eux. Cet ADR.
- 3b — dépendances, ordre de démarrage, arrêt en ordre inverse. Un ADR à part, dont les questions sont listées dans l’instruction et restent ouvertes.
Décision
D1 — Le lanceur supervise ce qu’il trouve, pas ce qu’on lui dit
Il balaie la racine des bundles de service — /var/lib/air/services/<bundleId>/, disposition
déjà tranchée par ADR-010 — et supervise ce qui y est installé.
Installer un service, c’est y déposer son bundle et émettre son octroi. Aucune notion nouvelle, aucun fichier de plus, et cela rejoint le provisionnement (ADR-121).
Ce que cela change, et qu’il faut assumer : le lanceur ne supervise plus ce qu’on lui dit, mais ce qu’il trouve. Un bundle mal installé devient un service qui démarre. C’est acceptable parce que l’octroi arbitre : un bundle sans octroi valide ne se lance pas, et le refus est celui d’ADR-150 — muet vers le demandeur, tracé au journal.
D2 — Un bloc service dans le manifeste dit comment le service démarre
Le schéma porte aujourd’hui une liste d’exécutables — « vide ⇒ rien à lancer » — sans dire lequel démarre, et la politique de redémarrage n’a aucun foyer. Un bundle balayé doit pourtant le dire.
Le manifeste gagne donc un bloc service optionnel : le point d’entrée (chemin
relatif, choisi parmi les executables déjà empreintés), ses arguments, et sa
politique de redémarrage.
Un bundle de genre service sans ce bloc n’est pas supervisé : il n’a pas dit comment
démarrer, et le deviner serait exactement la tolérance que le Principe 11 refuse.
D3 — Démarrer n’est pas un privilège : le bloc service n’est pas un entitlement
C’est le point qui décide de sa place. Le bloc service est une déclaration, pas une
demande de droit : l’octroi n’a rien à y accorder, et l’invariant demande↔octroi ne s’y
applique pas.
La raison tient en une phrase : ce qu’un service exécute est déjà borné ailleurs — par
son octroi (les familles), par sa cage (seccomp + Landlock), et par le fait que le point
d’entrée doit figurer parmi les executables empreintés du manifeste. Faire accorder en
plus « le droit de démarrer » n’ajouterait aucune borne et donnerait à l’administrateur un
réglage sans conséquence — c’est-à-dire un réglage qu’il finirait par poser sans le lire.
Le développeur est d’ailleurs le seul à savoir comment son propre service démarre. Le lui faire demander à la machine serait une cérémonie, pas un contrôle.
D4 — Le lancement par ligne de commande devient un second chemin, marqué
Le mode « un service, décrit en argv » ne disparaît pas : il sert au diagnostic et aux
tests. Mais il cesse d’être la façon de lancer un service, et devient donc un second
chemin au sens d’ADR-149 D10 — à
marquer d’un // SECOND-PATH: à son point d’usage, disant la voie normale, pourquoi elle
n’est pas empruntée, et à quelle condition la dérogation tomberait.
Ne pas le marquer reproduirait exactement ce qu’ADR-153 a eu à défaire pour le champ 7 de
/etc/passwd : un chemin d’exécution parallèle que personne n’avait reconnu comme tel.
D5 — L’ensemble supervisé est borné
Le nombre de services simultanés a une borne explicite, et le ppoll de la boucle vit sur la
pile — comme celui du monitor d’air-sshd. Un lanceur dont la table de surveillance
croîtrait avec le contenu d’un répertoire ferait dépendre son allocation de ce qu’un tiers y
dépose.
Conséquences
La supervision passe d’un processus à N. Un ppoll sur N canaux de lancement, N pidfds
et l’ordre d’arrêt. Les écoutes, déjà tenues par le lanceur (temps 3c), se répartissent par
service. La politique de redémarrage est par service et existe déjà.
L’arrêt du lanceur arrête tous ses services, et rend un verdict d’ensemble. Sans ordre inverse : c’est 3b.
2b-ii est livrée dans la foulée (2026-08-13). Le lanceur tient un AirRegistry ; launch
crée une paire de sockets par service et en dépose un bout avant le fork, par le genre 6 du
canal de lancement ; la boucle surveille l’autre bout et sert Register/Lookup ; admit pose
la politique au lancement et forget la reprend à la mort constatée — jamais à l’arrêt
demandé, un service qui refuse de s’arrêter gardant son pid donc ses droits.
Trois conséquences valent d’être dites :
- La capability est remise à TOUS les services, y compris ceux dont l’octroi ne porte aucune
règle
aircom. La tenir n’accorde rien ; une remise conditionnelle obligerait le service à deviner s’il doit l’attendre, donc à tolérer les deux cas, donc à pouvoir se tromper. - Le refus ne distingue « inconnu » de « interdit » que pour un demandeur habilité sur ce
nom-là. À tout autre,
Denied— que le nom soit vivant ou inventé. C’est là qu’est l’anti-énumération. La distinguer pour l’habilité n’est pas une fuite : il n’apprend rien qu’on ne lui ait accordé, et sans elle il ne pourrait pas séparer « pas encore démarré, je réessaie » de « jamais ». - Le mur du plafond
aircomtombe, et avec lui le dernier de la liste d’ADR-150 option B : plus aucune famille d’octroi n’est refusée faute d’applicateur.
Le vocabulaire du manifeste s’étend — un bloc de plus dans le schéma Cap’n Proto. Additif, même forme que la cinquième famille d’ADR-153 : les manifestes existants restent lisibles, le bloc étant optionnel.
Un service qui ne déclare rien n’est pas supervisé, et cela doit se voir : le journal nomme le bundle écarté et pourquoi. Un balayage silencieux qui ignore la moitié de ce qu’il trouve est pire qu’un balayage qui refuse.
Alternatives rejetées
- Une configuration binaire énumérant les services. Conforme au Principe 11, mais crée une seconde source de vérité à côté des bundles installés : un service présent sur disque mais absent de la liste — ou l’inverse — et il faut décider lequel gagne. C’est précisément la divergence qu’on supprime ailleurs.
- La ligne de commande, répétée. Ne préjuge de rien et se code vite, mais un système ne
se décrit pas en
argv: il aurait fallu tout reprendre au temps 3b. - Un exécutable unique imposé par bundle de service, pour éviter d’étendre le schéma. Rejeté : cela interdirait les binaires auxiliaires, que la plupart des services réels ont — la simplicité se serait payée en contorsions d’empaquetage.
- Mettre le point d’entrée dans l’octroi. Rejeté par D3 : démarrer n’est pas un privilège, et le développeur est le seul à savoir comment son service démarre.
Ce qui reste ouvert (3b)
Listé dans l’instruction, et non tranché ici : où vivent les dépendances (manifeste ou octroi), ce que « prêt » veut dire pour une dépendance, ce qu’on fait d’un service dont une dépendance meurt, et si l’arrêt se fait en ordre inverse.
Licence du document : MPL 2.0