Rapport de design — AirCom (IPC natif d’Air), incrément 0
Statut : note de travail exploratoire (non engageante). Cadre la conception d’AirCom — l’IPC natif d’Air (ADR-001, décision immuable) — avant de l’implémenter, sur le modèle du travail réseau (
reseau-architecture-crates-fr.md) qui a précédé ADR-091. Livre : une reconnaissance de l’existant couche 0/1/2, un design sans-IO, un squelette buildé + prouvé, et les questions ouvertes pour l’ADR/spec. AirCom mérite une spec dédiée (ADR-001 §« Statut futur » l’annonce) ; ce rapport en est le matériau d’entrée.Décisions structurantes émergentes → à trancher par le BDFL (voir §7) : le wire format (recommandation : Cap’n Proto, déjà tranché ADR-040), le modèle de capability (recommandation : fd-vers-connexion
SCM_RIGHTS), le découpage/nommage des crates, et quatre lacunes de primitives couche 1 qui conditionnent l’IPC complet. Conformément à la consigne de mission, on s’arrête au design pour ces points — on ne bâtit pas l’IPC complet.
Auteur : Thierry DELHAISE. Date : 2026-07-12.
1. Résumé exécutif (recommandations)
| Sujet | Recommandation | Base | Confiance |
|---|---|---|---|
| Wire format | Cap’n Proto (schema-first, zero-copy, versioning ABI). Codec placeholder au squelette tant que le schéma n’est pas figé. | ADR-040 (déjà tranché), ADR-001, exception 80 % mutualisée | Haute — déjà décidé |
| Control plane | Unix SOCK_SEQPACKET (frontières préservées + FD passing). Piloté par air-async (io_uring). | macro-architecture §B, ADR-001 | Haute |
| Data plane | memfd_create + mmap SHARED + F_ADD_SEALS, fd partagé via SCM_RIGHTS. Zero-copy. | ADR-001, macro-arch | Haute |
| Capability | Un fd vers une connexion AirCom = la capability. Unforgeable par sémantique noyau ; pas de token/crypto. Révocation = fermeture fd ; délégation = re-SCM_RIGHTS. | ADR-010, macro-arch §B, ADR-001 §117 | Haute |
| Découpage | Deux crates : air-com-proto (cœur sans-IO, no_std+alloc, fuzzable) + air-com (pilote air-async). | ADR-091 §2.1/§2.3 ; renforcé par air-async = crate std | Moyenne (nommage à trancher) |
| Anatomie | Motif sans-IO 9 composants (ADR-091), absences marquées. | ADR-091 (normatif) | Haute |
Quatre lacunes de primitives couche 1 bloquent l’IPC complet (pas le squelette). Aucune ne requiert le descellement de la couche 0 (les primitives kernel existent déjà, scellées) — ce sont des façades/ops additives couche 1/2, mais elles conditionnent les incréments suivants. Détail §6. On s’arrête au design ici.
2. §A — Reconnaissance de l’existant
2.1 Transport — ce qui existe
air-socket (couche 1) — sockets BSD synchrones, no_std, zéro-C.
- Unix domain sockets :
AirUnixStream(SOCK_STREAM),AirUnixListener,AirUnixDatagram(SOCK_DGRAM) —crates/air-socket/src/unix.rs:42/47/52. AdressesAirUnixAddress::from_pathetfrom_abstract(namespace abstrait) —address.rs:236/253. - FD passing
SCM_RIGHTS:AirUnixStream::send_fd(BorrowedFd)/recv_fd() -> OwnedFd—unix.rs:100/119(viasendmsg/recvmsg,CMSG_CLOEXEC). Surface généraleengine::{send_message, receive_message}(engine.rs:189/202) +socket_pair(domain, kind, proto)(engine.rs:312). - Précurseur sans-IO : le client DNS — codec pur
crate::dns(build_query/parse_response, fuzzé) séparé du piloteresolver.rs. C’est le patron à reproduire (ADR-091 §… le nomme explicitement). - Modèle d’ownership des fds : le fd est possédé par le registre
air-handle(engine.rs:302/318) ;AirSocketn’expose queas_fd()(BorrowedFd) etinto_raw_fd()(RawFd, le registre garde l’ownership).
air-async (couche 2) + air-uring (couche 1) — l’exécuteur io_uring que
consomme AirCom (son 1er gros consommateur).
- Exécuteur :
Runtime::{new, spawn, block_on, handle}+block_onlibre (crates/air-async/src/lib.rs:441/551/576/419). Mono-thread,!Send. - I/O socket :
TcpListener/TcpStream— noms « Tcp » mais agnostiques au fd (from_owned(OwnedFd),accept/read/write=IORING_OP_ACCEPT/RECV/SENDsur unBorrowedFd). AF_UNIX fonctionne aujourd’hui viafrom_owned(les tests d’air-asyncle prouvent :tests.rswrappe une paireUnixStream). - Multishot + provided buffers :
incoming()(accept multishot),recv_multishot(&BufferGroup)(net.rs:70/168). - Timers : roue de timers (
sleep/interval/timeout), fichiers (read_at/write_at/fsync). - Ops io_uring exposées par
air-uring(arm_*) :timeout, socket, connect, accept, receive, send, read, write, fsync, accept_multishot, receive_multishot(crates/air-uring/src/lib.rs:643..930).
Data plane (memfd/mmap) — couche 0 (air-sys-syscall::mem, ::fs) :
memfd_create(&CStr, MemfdFlags)(mem.rs:1051, flagsCLOEXEC|ALLOW_SEALING),ftruncate(fs.rs:1980),mmap_file(fd, len, off, prot, MapFlags::SHARED)(mem.rs:274),munmap(mem.rs:695),add_seals/get_seals(F_ADD_SEALS,fs.rs:2486/2504),MmapRegion::from_file(région partagéeArc, io_uring-compatible, avecfutex_wordintra-région —mem.rs:510/601).
Wire format — Cap’n Proto déjà dans le workspace :
capnp = "=0.26.0" (pin strict, pur Rust, zéro dep transitive, zéro C),
consommé par air-config-schema / air-config-compile. Codegen commité
(regenerate.sh + capnpc hors du chemin de build critique, ADR-025). Décidé par
ADR-040, explicitement mutualisé avec AirCom.
Capabilities — la doctrine (aucun code encore) :
- ADR-010 : entitlements déclaratifs signés, deny-by-default, 4 familles (dont
AirCom) ;
air-launchd(couche 5) applique au lancement et distribue les capabilities AirCom initiales par FD passing. « Pas d’autorité ambiante ». - macro-architecture §B (le plus précis) : « Une capability AirCom est un handle
opaque non-falsifiable. Techniquement : FD vers une connexion AirCom, plus
métadonnées de type » ; control plane =
SEQPACKET; délégation = re-SCM_RIGHTS(modèle seL4 / Mach ports) ; révocation = fermeture fd ; capability implicite universelle =air-registry.
2.2 Transport — ce qui manque (lacunes couche 1)
Voir §6 pour le détail et l’impact. En bref :
- Aucune façade couche 1 sur
memfd/mmap(les primitives sont couche 0, scellées ;air-comcouche 2 ne peut pas les toucher —check-layers). - Aucun FD passing asynchrone :
air-uringn’a pas d’arm_recvmsg/arm_sendmsg(le passage de fdSCM_RIGHTSsur io_uring n’existe pas). Seul le chemin synchroneair-socketexiste. - Aucun wrapper
SEQPACKETdansair-socket(couche 0 a bienSocketType::SeqPacket = 5, mais L1 ne l’enveloppe pas). - Aucun pont
OwnedFdentreair-socket(fds possédés par le registre) etair-async(from_owned(OwnedFd)) — modèles d’ownership incompatibles.
3. §A (suite) — décisions de reconnaissance
3.1 Wire format : Cap’n Proto (recommandé — déjà tranché)
Cap’n Proto satisfait toutes les exigences AirCom d’ADR-001 : schema-first,
versioning à règles testables en CI (ADR-012), zero-copy natif (mmap du data
plane), FD passing de 1ʳᵉ classe (tables de capabilities capnp). La dépendance est
déjà auditée et mutualisée (règle 80 %, docs/EXCEPTIONS.md) — aucune
nouvelle exception pour AirCom. Le workflow codegen commité (regenerate.sh) est
prouvé sur air-config-schema et réutilisable tel quel.
Le cœur (Framer/Codec) reste sans-IO : capnp est un runtime de (dé)sérialisation octets↔message pur, sans I/O — il vit dans le cœur
air-com-proto, fuzzable. Le squelette utilise un codec placeholder trivial (tag +u64) pour prouver la frontière Codec↔Framer↔StateMachine sans figer prématurément le schéma AirCom (le.capnpviendra avec la spec).
3.2 Modèle de capability : fd-vers-connexion, SCM_RIGHTS (recommandé)
Aligné mot pour mot sur macro-architecture §B et ADR-001 §117 :
- Une capability = un fd vers une connexion AirCom (
SEQPACKET), + métadonnées de type. Unforgeabilité par le noyau : on ne fabrique pas un fd qu’on n’a pas reçu. Pas de token, pas de nonce, pas de crypto, pas dememfd/sealing pour la capability elle-même (le sealing sert au data plane). - Distribution initiale par
air-launchd(couche 5) viaSCM_RIGHTSselon les entitlements signés. Délégation p2p par re-SCM_RIGHTSà travers AirCom lui-même. Révocation par fermeture du fd côté serveur. - Renfort : la sandbox io_uring couche 0 (
RestrictionSet::from_entitlements,io_uring/sandbox.rs:125) est le « double verrou » — la couche 5 traduira les entitlements signés enRestrictionSet. (Non implémenté ; point d’intégration.)
Conséquence pour le cœur sans-IO : le cœur ne détient aucun fd. Le Session Context (#8) porte les métadonnées de capability (le quoi) ; le pilote détient les fds (le comment). Le squelette matérialise cette frontière (
SessionContext::grant_capability, placeholder).
4. §B — Design sans-IO d’AirCom (le cœur du rapport)
4.1 Deux étages (ADR-091 §2.1)
┌──────────────────────── air-com-proto (couche 2) ────────────────────────┐
octets ───▶ │ Framer → Codec → StateMachine → (Session Context) │ ───▶ messages typés
(feed) │ CŒUR SANS-IO — pur, no_std+alloc, FUZZABLE, zéro socket/async/horloge │ + octets (poll_transmit)
└───────────────────────────────────────────────────────────────────────────┘
▲ API (pas d'I/O)
┌──────────────────────────────────┴──────── air-com (couche 2) ────────────┐
│ PILOTE I/O — la SEULE partie qui touche un socket : câble le cœur à │
│ air-async (io_uring). Control plane (SEQPACKET) + data plane (memfd). │
└───────────────────────────────────────────────────────────────────────────┘
▲
air-async (io_uring) / air-socket
4.2 Anatomie canonique à 9 composants appliquée à AirCom
| # | Composant | Rôle dans AirCom | Squelette | Cible (spec) |
|---|---|---|---|---|
| 1 | Framer | Délimiter les messages control-plane | ✅ préfixe de longueur u32 (transport flux) | Trame = 1 recvmsg sur SEQPACKET (le noyau délimite ⇒ Framer trivial) |
| 2 | Codec | (dé)sérialiser méthode/args ↔ message typé | ✅ placeholder Ping/Pong (tag+u64) | Cap’n Proto (schema-first, versioning ADR-012) |
| 3 | StateMachine | États connexion + requête/réponse | ✅ discipline requête→réponse | Multi-états : ouverture/handshake, requêtes concurrentes, fermeture, erreurs protocolaires |
| 4 | Handshaker | Établissement + négociation | ⛔ marqué absent | Négociation de version de schéma + jeu d’extensions (la capability, elle, est déjà établie via SCM_RIGHTS) |
| 5 | Flow Controller | Back-pressure explicite (reactive streams) | ⛔ marqué absent | Crédits/fenêtres pour le streaming data-plane (ADR-001 §back-pressure) |
| 6 | Multiplexer | Plusieurs requêtes/canaux sur un fd | ⛔ marqué absent | Ids de requête, isolation inter-canaux (façon canaux SSH / streams h2) |
| 7 | Timer Manager | Échéances (timeout requête, keepalive) | ⛔ marqué absent (dans le cœur) | Horloge injectée dans le cœur ; roue air-async dans le pilote |
| 8 | Session Context | État/params/capabilities par connexion | ✅ role/id + compteur de capabilities | Métadonnées de capabilities négociées, secrets zeroïsés au drop, zéro fuite inter-sessions |
| 9 | Extension hooks | Points d’extension (entitlements, négos) | ⛔ marqué absent | Registre de hooks à interface étroite (entitlements ADR-010, extensions de schéma) |
Les 5 absences sont marquées explicitement (marqueurs unité
channel::{Handshaker, FlowController, Multiplexer, TimerManager, ExtensionHooks}
- commentaires
// NO …) — « on retrouve toujours les mêmes cases » (ADR-091 §2 règle 3).
4.3 Control plane vs data plane (le découpage clé d’AirCom)
- Control plane — petits messages de signalisation/appel de méthode. Transport
SEQPACKET(frontières préservées, FD passing). C’est le domaine du cœur sans-IO (Framer/Codec/State) — la surface hostile fuzzée. - Data plane — charges volumineuses (images, frames, buffers GPU). Transport
memfd+mmapSHARED(scellé), dont le fd est passé sur le control plane parSCM_RIGHTS: zero-copy inter-processus. Le cœur ne voit que la référence (id/offset/len) ; le pilote fait lemmap. Back-pressure explicite (Flow Controller, #5) pour le streaming.
Cette séparation est la raison d’être du refus de D-Bus (ADR-001 §« zero-copy et back-pressure ») : le control plane reste léger, le data plane ne copie jamais via un broker.
4.4 Découpage en crates (recommandé)
Deux crates (choix retenu, cf. §7 pour le nommage) :
air-com-proto(couche 2,#![no_std]+alloc) — le cœur sans-IO. Deps :air-base-coreseulement (+capnpà terme). Fuzzable en isolation (fuzz/: une cible par parseur — Framer, Codec, Handshaker).air-com(couche 2,std) — le pilote surair-async. Deps :air-com-proto,air-async,air-sys-types.
Pourquoi deux crates plutôt qu’un seul proto/+io/ (les deux sont permis par
ADR-091 §2.3) : air-async est un crate std (Rc/RefCell/thread-locals) ; un
crate unique forcerait le cœur à hériter de std et empêcherait le no_std
visé par ADR-091, et ferait entrer air-async dans le build de fuzz du cœur. La
séparation en crates garantit un cœur no_std réellement isolé (fuzz sans
socket, sans exécuteur). C’est aussi le précédent quinn-proto/quinn.
5. §C — Squelette minimal + preuve (livré, host gnu)
Crates créés ([package.metadata.air] layer = 2 pour les deux) :
crates/air-com-proto/ — CŒUR SANS-IO (no_std + alloc)
src/framer.rs (1) préfixe de longueur, borné MAX_FRAME_LEN, fuzz-ready
src/codec.rs (2) Ping/Pong placeholder (cible : Cap'n Proto)
src/state.rs (3) machine requête/réponse (transitions illégales rejetées)
src/session.rs (8) Session Context (role/id/capabilities)
src/channel.rs assemblage feed/poll_transmit + marqueurs d'absence 4/5/6/7/9
crates/air-com/ — PILOTE I/O (std, sur air-async)
src/io.rs serve_pong / request_pong / unix_stream_pair (+ tests)
examples/ping_pong.rs — PREUVE exécutable
Preuve — aller-retour Ping → Pong entre deux services in-process sur une
paire de sockets Unix pilotée par air-async (io_uring) :
$ cargo run -p air-com --example ping_pong
AirCom incrément 0 — Ping(0x0a1cc0de12345678) → Pong OK (1 requête servie)
$ echo $?
0
Portes vertes (host x86_64-unknown-linux-gnu, carbon) :
cargo fmt --check✅ ·cargo clippy -- -D warnings✅ (lintsdenyarithmétique/cast respectés :checked_*/try_from, zéroas).cargo test✅ — 19 tests (18 cœur : framer/codec/state/session/channel ; 1 pilote : round-trip de bout en bout) + 3 tests pilote additionnels (2 allers- retours, pair fermé, mapping errno).cargo xtask check-layersVERT ✅ —air-com/air-com-protone sautent pas en couche 0 (arêtes 2→2 et 2→1 conformesM ∈ {N-1,N}). Le seul advisory restant est l’escape hatch cible-only préexistant d’air-libc-capi(ADR-087).cargo machete✅ (zéro dep inutilisée). Zéro dépendance externe.- Couverture ✅ — cœur
air-com-proto94–100 %/fichier (session.rs/state.rs= 100 %) ; piloteair-com/io.rs= 93 % régions / 97 % lignes / 100 % fonctions (> 90 %, cible couche 2). Reste non couvert : les branches d’échec transport rares (writepartiel à 0, échecsocketpair).
Un seul bloc unsafe dans tout le livrable : la conversion de fd bootstrap
std OwnedFd → air OwnedFd (io.rs, // SAFETY: présent, idiome copié des
tests d’air-async). Le cœur air-com-proto n’expose aucune fn unsafe.
Choix de squelette assumés (et pourquoi) :
- Transport =
SOCK_STREAM(pasSEQPACKET) :air-socketn’a pas de wrapperSEQPACKET(lacune §6.3) ; on re-délimite au Framer. - Bootstrap de la paire via
std::os::unix(pasair-socket) : pas de pontOwnedFdpropre depuisair-socket(lacune §6.4).air-asyncpilote ensuite toute l’I/O — c’est bien « piloté parair-async». - Pas de FD passing dans la preuve : le passage de fd asynchrone n’existe pas
(lacune §6.2) et le data plane
memfdn’a pas de façade L1 (lacune §6.1). Les démontrer proprement (dans les règles de couche) exige de combler ces lacunes d’abord — d’où l’arrêt au design.
6. Lacunes de primitives couche 1 (STOP-and-report)
Aucune de ces lacunes ne requiert le descellement d’une couche 0 scellée : les
primitives kernel (memfd/mmap/seal/sendmsg/recvmsg/SEQPACKET) existent
toutes en couche 0 et y sont scellées/testées. Ce sont des façades/ops
additives couche 1 (et une future arête couche 1→0 dans air-uring). Mais elles
conditionnent l’IPC complet — d’où le report avant de bâtir.
6.1 Façade couche 1 de mémoire partagée (memfd + mmap + seals) — manquante
Les primitives sont couche 0 (air-sys-syscall::mem/::fs). air-com
(couche 2) ne peut pas les appeler (check-layers interdit 2→0). air-memory
(couche 1) n’expose qu’un raw_mapping anonyme (piles de threads) ; son backing
mmap/MmapRegion est explicitement différé (backing.rs, « heap en v1 »).
→ Requis : un module/crate couche 1 « shm » packageant memfd_create → ftruncate → mmap SHARED → add_seals → poignée typée » (candidat : étendre air-memory, ou un air-shmdédié). **Bloque le data plane zero-copy.** Envisager aussi une varianteAirHandleKind::Memfd/Shmdansair-handle`.
6.2 FD passing asynchrone (recvmsg/sendmsg io_uring) — manquant
air-uring expose arm_{socket,connect,accept,receive,send,read,write,fsync,…}
mais pas arm_recvmsg/arm_sendmsg (donc pas de SCM_RIGHTS sur io_uring), et
aucun escape-hatch SQE brut. Le passage de capability/fd async est donc
impossible sans ajout. → Requis : arm_sendmsg/arm_recvmsg (+ take_*) dans
air-uring (couche 1, arête 1→0 conforme) + futures SendMsg/RecvMsg dans
air-async (couche 2). Le chemin synchrone existe (air-socket::AirUnixStream:: {send_fd,recv_fd}) et peut servir de secours pour le bootstrap de air-launchd,
mais l’IPC en régime établi veut l’async. Bloque les capabilities async +
data-plane fd async.
6.3 Wrapper SEQPACKET dans air-socket — manquant
La couche 0 a SocketType::SeqPacket = 5 mais air-socket n’enveloppe que
SOCK_STREAM/SOCK_DGRAM (unix.rs). Le control plane AirCom veut SEQPACKET
(frontières préservées ⇒ Framer trivial, FD passing). → Requis : un
AirUnixSeqpacket (ou paramétrer les wrappers Unix par SocketType). Additif
couche 1. Contournement squelette : SOCK_STREAM + Framer à préfixe.
6.4 Pont OwnedFd air-socket ↔ air-async — manquant
air-socket possède ses fds via le registre air-handle (into_raw_fd garde
l’ownership) ; air-async::TcpStream::from_owned veut un OwnedFd. Il n’y a pas
de conversion propre (risque de double-close). → Requis : air-socket exposant
un into_owned_fd() (via air_handle::AirHandle::into_owned_fd, qui existe), ou
air-async acceptant une source de fd du registre. Additif couche 1.
Contournement squelette : bootstrap via std::os::unix (idiome des tests
air-async).
Note de gouvernance :
air-socket(couche 1, campagne ADR-069) est vraisemblablement scellé — 6.3 et 6.4 le touchent et pourraient donc exiger un descellement couche 1 (décision BDFL), contrairement à 6.1/6.2 qui sont des ajouts sur des crates couche 1/2 en cours de construction (air-memorypartiel ;air-uring/air-asyncincréments actifs). À arbitrer.
6.5. Lacunes couche 1 comblées (mise en œuvre — branche feat/aircom-couche1-gaps)
Statut : fait. Les quatre lacunes du §6 sont comblées, host-first, à rigueur couche 1 (100 % de couverture lignes sur le neuf, aucune fonction
unsafeexposée,check-layersvert). Aucun descellement couche 0 (les primitives kernel existaient déjà, scellées). Le seul descellement est couche 1 additif surair-socket(§6.3/6.4) — zéro changement sémantique des API STREAM/DGRAM existantes ; à re-scellercouche-1-v2.1après revue BDFL.
| # | Lacune | Solution (crate, couche) | API ajoutée | Preuve |
|---|---|---|---|---|
| 1 | Façade shm | air-shm (nouvelle crate, couche 1) | AirShmSegment (create/from_owned_fd/add_seals/seals/as_fd/into_owned_fd/map_readonly/map_writable) + AirShmMap/AirShmMapMut (vues possédées &[u8]/&mut [u8]) | 9 tests, 100 % lignes ; write→seal→read + adoption de fd |
| 2 | SCM_RIGHTS async | air-uring (couche 1) + air-async (couche 2) | Reactor::{arm_sendmsg,arm_recvmsg,take_sendmsg,take_recvmsg} ; TcpStream::{send_message,recv_message} (futures SendMsg/RecvMsg) | envoi/réception d’un fd async en loopback socketpair (couche 1) + aller-retour in-process (couche 2) ; orphelin recvmsg annulé et libéré au CQE |
| 3 | SEQPACKET | air-socket (couche 1, descellement additif) | AirUnixSeqpacket / AirUnixSeqpacketListener (connect/pair/bind/accept/send/recv/send_fd/recv_fd) | 11 tests : frontières préservées, troncature, FD passing, accept |
| 4 | Pont OwnedFd | air-socket (couche 1, descellement additif) | AirSocket::{into_owned_fd,from_owned_fd} (+ sur AirUnixStream/AirUnixSeqpacket) | aller-retour registre→sortie→ré-adoption sans double-close |
Choix de conception clés :
- Lacune 1 → crate dédiée
air-shm, pasair-memory.air-memoryest un crate d’allocateurs (arène/pool/slab) + comptabilité ; un segmentmemfdpartageable (un fd, pas un allocateur) est un concern distinct.air-shm: zéro dépendance externe,no_std+alloc,air-base-core/air-sys-syscall/air-sys-types. - Lacune 4 — NON, ce n’était pas un vrai conflit d’ownership. Le diagnostic
redouté (« registre
air-handlevs ownershipair-asyncincompatibles ») ne se matérialise pas : le primitifair_handle::AirHandle::into_owned_fdexiste déjà (ADR-069, pensé pour l’interop) — il retire l’entrée du registre et transfère l’ownership seul auOwnedFd(pas de double-close), et le type de fd est identique des deux côtés (air_sys_types::fd::OwnedFd). Le pont est donc un simple move :AirSocket::into_owned_fd → TcpStream::from_owned. Contrepartie assumée et documentée : le fd cédé devient invisible àair_handle::live_count/kind_ofet auclose()de la façade libc — c’est le rôle d’un escape-hatch d’interop. Pas de STOP nécessaire. - Lacune 2 — couche 0 déjà prête.
air_sys_syscallexposait déjàsubmit_send_message/submit_receive_message(SCM_RIGHTS,OwnedOp::SendMsg/RecvMsg, modèle orphelin S1) ; l’ajout couche 1 est une paire de façades mincesarm_*/take_*, arête 1→0 conforme — zéro descellement couche 0.
Preuve d’intégration réunissant les 4 lacunes : air-com::capability
(capability_and_shm_dataplane_roundtrip) — un aller-retour AirCom entre deux
services in-process passe un fd (capability) par SCM_RIGHTS async sur un
control plane SEQPACKET (bridgé OwnedFd) portant un segment shm scellé
zero-copy ; le pair mmap le segment via le fd reçu et lit la charge utile
(assert d’égalité). Seul le fd a traversé le socket — la charge n’est jamais copiée.
Barrière : check-layers vert (55 crates, air-shm=1 ; seul advisory =
l’escape-hatch cible-only préexistant d’air-libc-capi, ADR-087) ; fmt/clippy -D/tests verts ; machete vert. La couverture root/sudo de la barrière
complète est laissée à l’arbitrage CI (la couverture lignes per-crate du neuf
est à 100 % en local).
7. Questions ouvertes pour l’ADR / la spec AirCom
- Nommage des crates — à trancher (ADR-029). La macro-architecture §B nomme
air-aircom(transport+runtime),air-aircom-schema(compilateur),air-aircom-codegen,air-registry. La mission et ce squelette ont utiliséair-com(pilote) +air-com-proto(cœur). Divergence à réconcilier : garderair-aircom(cohérence macro-arch) ou acterair-com/air-com-proto(+air-com-schema) ? La spec AirCom doit figer le jeu de crates (proto/pilote/schema/codegen/registry) et leurs couches. - Codec Cap’n Proto : figer le premier schéma AirCom (
.capnp) et la politique de versioning (champs réservés,InterfaceV2coexistant — ADR-012). Réutiliser le workflowregenerate.sh/codegen commité d’air-config-schema. - Modèle d’objet (ADR-002) : AirCom transporte des invocations sur des
AirObject. Articuler la StateMachine (#3) + Multiplexer (#6) avec le modèle d’objet C-ABI et lecx.capability::<T>()de macro-arch §B. air-registry(couche 5) : format du handshake de découverte, capability implicite universelle, one-fd-par-service vs canal bootstrap multiplexé (macro-arch laisse ouvert).- Transport control plane : confirmer
SEQPACKET(vsSTREAM+framing) et trancher la lacune §6.3. - Data plane : conventions de
memfd(taille, sealing obligatoire ?F_SEAL_WRITEpour un partage read-only ?), et l’API L1 shm (§6.1). - Sync vs async (ADR-038) : AirCom expose-t-il une API synchrone de 1ʳᵉ
classe (via
air-socket) en plus de l’async (air-async) ? Le cœur sans-IO le permet « gratuitement ». - Régime de fuzz (ADR-091 §5) : cibles
fuzz/par parseur (Framer, Codec capnp, futur Handshaker) ; model-based testing de la StateMachine ; virtual clock pour le Timer.
8. Conclusion
AirCom s’inscrit proprement sur l’existant : air-async pilote AF_UNIX
aujourd’hui, Cap’n Proto est déjà auditée et mutualisée, le modèle de capability
(fd + SCM_RIGHTS) est cohérent ADR-010/ADR-001, et le motif sans-IO ADR-091
s’applique naturellement (control plane fuzzable, data plane zero-copy à part). Le
squelette prouve la boucle cœur↔pilote↔io_uring de bout en bout (Ping→Pong,
exit 0, check-layers vert). Quatre lacunes de primitives couche 1 (façade shm,
FD passing async, SEQPACKET, pont OwnedFd) conditionnent l’IPC complet et
appellent des décisions d’arbitrage/descellement — on s’arrête donc au design,
avec une spec AirCom à ouvrir (ADR-001 l’annonce) pour figer nommage, schéma,
registre et transport.
Licence du document : MPL 2.0 — note de travail, exploratoire (non engageante).