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

ADR-077 — Managers de domaine en couche 1 (FileManager/NetworkManager/TerminalManager…) : surface objet médiatrice des toits (libc + PAL)

Statut : Accepté (2026-07-08, décision BDFL directe). Complète ADR-052 (objets couche 1), renforce la stratification (Principe 1). Encadre un descellement additif de la couche 1 (RFC, ADR-015/051/065).

Catégorie : Architecture des toits sur couche 1. Ajoute de l’API couche 1 (additif) ; n’altère aucune API existante.

Généralisé par ADR-149 (« Architecture de code : contrôleurs, managers de domaine, et contrat d’API », 2026-08-03). Le présent ADR traite des managers de couche 1 ; ADR-149 pose la doctrine dont ils sont un cas particulier — le contrôleur comme unité de responsabilité et de test, les trois rangs (manager public / contrôleur spécialisé / contrôleur interne jamais public), le contrat d’API réduit aux managers et types publics, et la règle « une crate porte au moins un contrôleur ». L’état de conformité relevé dans l’arbre reste ici, dans l’amendement de fin de document.

Contexte

Règle non négociable : les toits (libc C-ABI, PAL std::sys) s’appuient EXCLUSIVEMENT sur la couche 1, jamais sur la couche 0 (air-sys-syscall). Seule la couche 1 parle à la couche 0.

Un audit (campagne libc-pour-std, chantier B) révèle deux crates libc qui violent la règle : air-libc-fileio (8 appels air_sys_syscall::{fs,process,ipc,system}) et air-libc-socketio (9 appels air_sys_syscall::net — codec sockaddr + sockopt, descellés en couche 0 pour la libc par ADR-070/071 : le pattern même à proscrire). Les 4 autres crates libc sont déjà propres.

Par ailleurs, la couche 1 est incomplète pour la surface visée : air-filesystem a une API riche AirFile (fd) mais pas les opérations par chemin (mkdir/unlink/rename/symlink/readlink/chmod/chown/access), ni chdir/getcwd ; air-socket/air-handle n’exposent pas le codec sockaddr ni les sockopt.

Décision

1. Ce qui manque en couche 1 s’implémente via une VRAIE API OBJET — des Managers de domaine. La couche 1 expose, par domaine, un objet Manager médiateur qui est LA surface consommée par les toits :

  • AirFileManager (air-filesystem) — tout l’I/O fichier : ouverture, ops par chemin (mkdir/unlink/rename/symlink/readlink/chmod/chown/access/hardlink/mkfifo/truncate/sync), répertoires, métadonnées par chemin, chdir/getcwd, fabrique de temporaires.
  • AirNetworkManager (air-socket/air-handle) — tout le réseau : sockets, codec sockaddr ↔ octets, sockopt, résolution de noms, transfert.
  • AirTerminalManager (air-terminal) — termios/tty/PTY (cf. ADR-060/061).
  • … (par domaine : Process, Time…), au fil des besoins.

Le Manager prend/rend des types couche 1 (AirPath, Mode, AirResult, newtypes FD), jamais des types C. Il coordonne les briques existantes (AirFile, registre air-handle, AirSocket…) et la couche 0. La libc et le PAL bindent le Manager (le shim libc n’y ajoute que l’adaptation C-ABI : CStrAirPath, errnoAirResult). Une brique couche 1 → 2 faces ([ADR-051] doctrine dual-face).

2. Correction en UN SEUL cycle de descellement. (a) Desceller la couche 1 une fois ; (b) implémenter les Managers manquants (AirFileManager, AirNetworkManager) et recâbler air-libc-fileio + air-libc-socketio dessus, en retirant leur dépendance air-sys-syscall ; (c) tests (barrière verte, couverture couche 1 à 100 %) ; (d) si OK, re-sceller la couche 1 une fois (couche-1-v1.x). Pas de descellements multiples en série.

Conséquences

  • ADR-070/071 révisés en pratique : le codec sockaddr et les sockopt restent en couche 0 (ils y sont légitimes comme wrappers typés), mais la libc ne les appelle plus directement — elle passe par AirNetworkManager (couche 1), qui, lui, peut consommer la couche 0. La frontière proscrite (libc→couche 0) disparaît.
  • Fondation des 71 symboles std : la campagne libc-pour-std bind désormais les Managers ; les ops manquantes s’ajoutent au Manager (couche 1), puis au shim — jamais un raccourci couche 0.
  • Coût : descellement + re-sceau couche 1 (additif, gouverné) ; extension d’air-filesystem/air-socket (tests 100 %) ; recâblage de 2 crates libc (ABI C inchangée).
  • Réversibilité : purement additif côté couche 1 ; les toits gagnent une surface plus propre. Aucune API scellée retirée.

Alternatives rejetées

  • Free-functions couche 1 éparses (au lieu de Managers objet) : rejeté — la directive BDFL exige une API objet cohérente par domaine (découvrabilité, invariants portés par l’objet, parité libc/PAL).
  • Grand-fathering de air-libc-fileio/socketio : rejeté (choix BDFL « remédier maintenant »).
  • Laisser la libc taper la couche 0 (statu quo ADR-070/071) : rejeté — casse la stratification, l’instrumentation et la doctrine dual-face.

Amendement — état de conformité des Managers, relevé dans l’arbre (2026-08-03)

Demandé par le BDFL le 2026-08-03 : « reprendre chaque ADR nommant ces Managers et corriger pour mettre en conformité leur état, leur rôle, leur fonctionnement ». Cette section devient l’état faisant autorité ; les autres ADR y renvoient plutôt que de le dupliquer.

Le critère retenu — corrigé le 2026-08-03 (second passage)

Première formulation, trop faible. Le premier relevé demandait qu’« aucune opération du domaine ne soit atteinte par un toit autrement que par le Manager », et comptait les sites d’appel dans crates/air-libc-*. Ce critère mesure au mauvais endroit. Un toit qui, ce jour-là, n’emprunte pas une route ne prouve rien : la route existe, et le prochain binding — C++, Swift, un langage qu’on n’a pas prévu — la trouvera. Rappel du BDFL, 2026-08-03 : les Managers sont des contrôleurs de très haut niveau destinés à recevoir tous les appels des bindings, et à cacher l’implémentation interne. Une route non empruntée reste une fuite d’implémentation.

Le critère porte donc sur ce que la couche 1 expose, non sur ce que les toits appellent :

Un domaine est conforme si aucune opération du domaine n’est publique en dehors de son Manager. Le Manager peut exposer des types (AirSocket, PtyPair, AirDevice…) — c’est même sa monnaie. Ce qu’il ne doit pas laisser fuir, ce sont les opérations : ni module engine public, ni create/bind/listen appelables directement, mais AirNetworkManager::create_socket(), ::bind_socket(), ::listen_socket().

Corollaire, et il autorise ce qui existe. Une fonction en forme de C — openpty(), tcgetattr()a le droit d’exister, parce que la libc doit fournir ses fonctions standard. Mais elle n’est légitime que comme coquille de traduction : son corps doit appeler AirTerminalManager::open_pty(), et rien d’autre. Ce qu’ADR-077 §1 disait déjà — « le shim libc n’y ajoute que l’adaptation C-ABI » — et qu’il faut lire comme une interdiction du chemin parallèle, pas comme une simple préférence de style.

Le relevé est reproductible : pour chaque crate de couche 1, on liste les pub fn libres qui sont des opérations de domaine (par opposition aux helpers purs), et on les compare à la surface du Manager.

Ce que le relevé donne

Le modèle, pour situer les autres. AirFileManager porte 45 méthodes, et air-filesystem n’expose que trois fonctions libres — coalesce, map_event_kind, segment_matches — qui sont des helpers purs, pas des opérations de domaine. C’est la doctrine appliquée telle qu’elle est écrite.

DomaineContrôleurOpérations du domaine exposées hors du contrôleurVerdict
air-filesystemAirFileManager (45)0 — trois helpers purs seulementConforme. La référence
air-envAirEnvironmentManager (4)0 fonction libre publique dans la crate ; des doublons subsistent côté toitsQuasi conforme
air-systemAirSystemManager (3)0Conforme (domaine étroit)
air-memoryAirMemoryManager (3)raw_mappingUn trou
air-logAirLogManager (10)1Conforme ; clients en couche 2, pas des toits libc
air-signalAirSignalManager (2)12block, unblock, replace_mask, current_mask, pending, raise_signal, queue_signal, send_signal_to_process, send_signal_to_process_group, send_signal_to_thread, wait_for_signal, wait_for_signal_untilNon conforme
air-processAirTaskManager (16)14 — dont toute la séparation de privilèges : drop_privileges, drop_to_user, set_uids, set_gids, set_supplementary_groups, supplementary_groups, reduce_capabilities_to, close_inherited_fds_except ; plus spawn_process, exit_process, signal_by_pidfd, parent_death_signal, set_parent_death_signal, air_pipeNon conforme — et c’est le plus grave
air-socketAirNetworkManager (4, utilitaires)le module engine est public : create, bind, listen, connect, accept, send, receive, send_message, receive_message, shutdown, local_address, peer_address, peer_credentials, socket_pair, set_reuse_address, set_no_delay, set_keep_alive, set_nonblocking, is_connect_in_progress — plus AirSocket et ses 65 méthodesNon conforme
air-terminalaucun21openpty, open_pty_master, pty_peer, pty_number, pty_unlock, tcgetattr, tcsetattr, tcdrain, tcflow, tcflush, tcsendbreak, tcgetpgrp, tcsetpgrp, make_raw, isatty, ttyname, session_id, set_controlling_terminal, clear_controlling_terminal, window_size, set_window_sizeNon conforme, cas extrême

air-process est le plus préoccupant, et pas par le nombre. C’est la séparation de privilèges — Principe 10 — qui est intégralement hors contrôleur. drop_privileges et set_uids sont exactement ce qu’un contrôleur doit médier plutôt qu’offrir en libre-service : un binding qui les appelle directement contourne tout ce qu’un SecurityManager (ADR-089) pourrait un jour vouloir consulter.

AirTerminalManager::open_pty() n’existe pas — le contrôleur lui-même n’existe pas. Le chemin réel est air_terminal::openpty()air_terminal::open_pty_master()air_sys_syscall::terminal::open_pty_master() : trois étages, aucun contrôleur.

Point qui reste vrai : aucune violation de stratification. Aucun toit ne dépend d’air-sys-syscall, et une crate de couche 1 doit consommer la couche 0 — c’est son rôle. L’infraction n’est pas là : elle est au franchissement, dans ce que la couche 1 rend public.

Les Managers absents

ManagerCrateCe que le domaine exposeGravité
AirTerminalManagerair-terminalAirTerminal, PtyPair, AnsiCodec, mais l’essentiel en fonctions libres (openpty, pty_*, tcgetpgrp…)Forte — nommé explicitement au §1 du présent ADR. Rendez-vous au jalon « un shell sur console série »
AirSandboxManagerair-sandboxAirSandbox (poseur de profil, apply()), AirSandboxProfile, AirLandlockPolicy, deux fonctions de compilationMoyenneADR-112 le nomme dans son propre titre alors que le code livre AirSandbox. Bloqué par l’ADR sécurité dédié (cf. amendement D6 d’ADR-046)
AirDeviceManagerair-deviceAirDevice, AirDeviceEnumerator, AirDeviceMonitor, AirBlockDevicesurface objet complète, aucune fonction libreFaible — il manque le point d’entrée médiateur, pas les objets
AirKeystoreManagerair-keystoreDes types par artefact (AirHostKey, AirKnownHost, AirCaKey, AirKrl, AirRevocationStore, AirSshKeyPair…) et 25 fonctions libres par module (load, save, encode_artifact, decode_artifact, verify_host, is_trusted_ca, is_revoked)Moyenne, et d’une autre nature. ADR-108 §1 déclare ce domaine « manager de domaine au sens d’ADR-077 » et le manifeste affirmait une surface médiatrice « jamais des free-functions » — les deux étaient faux. Mais aucune de ces 25 fonctions n’atteint la couche 0 : elles passent toutes par AirFileManager. Ce n’est donc pas une route parallèle vers le noyau, c’est une façade manquante pour les bindings

Quatre Managers que la doctrine ne nommait pas

Le §1 énumérait AirFileManager, AirNetworkManager, AirTerminalManager. Le code en a produit quatre autres, légitimes mais jamais doctrinés : AirEnvironmentManager, AirSignalManager, AirMemoryManager, AirSystemManager. Ils sont par la présente reconnus comme Managers de domaine au sens de cet ADR, avec les mêmes obligations — dont celle de médier.

Noms périmés

  • AirProcessManager → renommé AirTaskManager par ADR-088std sur PAL safe en couche 1, sans C »), pour coller au vocabulaire du noyau : task + thread group. Le renommage est allé au bout dans le code ; des mentions résiduelles subsistaient dans les ADR et les manifestes au 2026-08-03.
  • AirThreadManagerjamais créé, dissous dans AirTaskManager par la même décision (fork et création de thread sont le même clone3, à des drapeaux près).

Ce que cet amendement ordonne, et ce qu’il laisse ouvert

Tranché par le BDFL le 2026-08-03 — les Managers absorbent leur domaine. La question posée par la première version de cet amendement (« absorber, ou rétrograder le Manager au rang d’utilitaire assumé ? ») est close dans le premier sens. Un Manager est un contrôleur de très haut niveau : il reçoit tous les appels des bindings — C, C++, Swift, et ce qui viendra — et il cache l’implémentation interne. Le §1 n’est donc pas à amender à la baisse ; c’est le code qui doit s’y conformer.

Ce qui en découle mécaniquement, et qui reste à ordonnancer :

  • air-terminal : écrire AirTerminalManager, y porter les 21 opérations, et ne garder les fonctions en forme de C que comme coquilles de traduction. Rendez-vous au jalon « un shell sur console série » ;
  • air-socket : dépublier le module engine, porter ses 19 opérations sur AirNetworkManager (create_socket, bind_socket, listen_socket…), AirSocket restant un type légitimement exposé ;
  • air-process : porter la séparation de privilèges sur AirTaskManager — c’est la plus urgente, parce qu’une opération de privilège en libre-service contourne par construction tout arbitrage de sécurité ultérieur ;
  • air-signal : porter les 12 opérations sur AirSignalManager ;
  • air-keystore, air-device, air-sandbox : écrire le contrôleur manquant.

Trois réserves subsistent, et elles ne sont pas de la même eau :

  1. AirSandboxManager est bloqué, non déprioritisé — sa surface dépend d’arbitrages renvoyés par l’amendement D6 d’ADR-046 à un ADR sécurité dédié.
  2. Le coût de re-scellement. La couche 1 est scellée ; déplacer des opérations d’une fonction libre vers un Manager change la surface publique. À mener en un seul cycle de descellement, comme le §2 l’a fait la première fois — pas en série.
  3. Les objets déjà bien formés (AirSocket, AirDevice, AirTerminal) ne sont pas en cause : ce sont des types, et un contrôleur a vocation à en rendre. Seules les opérations doivent cesser d’être joignables autrement que par lui.