ADR-114 — air-json : promotion du JSON minimal maison en crate couche 1 partagée (fuzzée, sans serde, os-free)
Statut : Accepté (2026-07-26, décision BDFL). RFC de direction
(ADR-015).
S’appuie sur ADR-099 (origine :
air-sshd/src/json.rs, interchange de config E.6), ADR-024
(règle des 80 % — pas de serde_json en production), ADR-088
(os-free), ADR-029 (nommage), ADR-019
(modèle d’erreurs).
Compagnon d’ l’ADR CLI air-keystore (vague 2 #3) : premier consommateur du mode JSON
agent-safe.
Catégorie : Architecture couche 1 (extraction d’une crate utilitaire). Vague 2 #2.
Contexte
Le triage OpenSSH a acté que la CLI air-keystore ([ADR-108], vague 2 #3) exposera un
mode JSON in/out agent-safe (entrée sur stdin, sortie structurée), pour qu’un outil ou
un agent LLM manipule le keystore sans le piège du parsing shell (simples/doubles/back
quotes). Il faut donc un codec JSON — mais pas serde_json : la doctrine config binaire
([ADR-073]) proscrit le JSON au runtime, et la règle des 80 % ([ADR-024]) déconseille une grosse
dépendance dont on n’utilise qu’une fraction, sur une cible os-free ([ADR-088]).
Or ce codec existe déjà : air-sshd/src/json.rs ([ADR-099] E.6) — un JsonValue + un
parseur borné (MAX_DEPTH, anti-débordement de pile) + un sérialiseur, faits main, sans
serde, qui ne paniquent jamais (Result), surface hostile fuzzée, périmètre entiers
(pas de flottants). Il est enfermé dans air-sshd alors qu’il devient un besoin partagé.
Directive BDFL (2026-07-26). « Ajouter à l’outil air-keystore une capacité de lire/écrire
JSON en entrée/sortie, pour qu’un outil (un agent LLM ?) manipule l’in/out de manière sûre, sans
problème de parsing de ligne de commande. » → et, en cadrage : extraire air-json comme
crate partagée fuzzée.
Décision
1. Extraire air-json — crate couche 1 partagée
Le contenu de air-sshd/src/json.rs est promu en crate air-json (couche 1),
utilitaire pur codec, sans I/O, no_std + alloc, erreurs AirError ([ADR-019]). Elle
est consommée par air-keystore (couche 1), air-sshd et air-ssh (couche 2) — un codec pur
en couche 1 est utilisable par les couches au-dessus. air-sshd cesse d’héberger le module
et dépend d’air-json (non-régression garantie par les tests d’interchange E.6 existants).
2. Pas de serde_json en production — codec possédé
air-json reste fait main (pas de serde/serde_json dans [dependencies] de
production) : cohérent [ADR-024] (on n’utilise pas 80 % de serde_json), [ADR-088] (os-free,
deps minimales), et [ADR-073] (le JSON n’est jamais lu au runtime — uniquement import/export
et mode CLI, hors chemin de démarrage). (serde reste permis en dev/test-only au titre
d’[ADR-030] si un oracle différentiel de fuzz l’exige.)
3. Périmètre conservé, sûreté renforcée
Périmètre inchangé : objets, tableaux, chaînes (échappements standards + \uXXXX BMP),
entiers i64 (fractionnaires/exponentiels refusés — Air n’en a pas besoin ;
Principe 5 : on n’ajoute les flottants que si un besoin réel apparaît), true/false/null.
Invariants durcis en tant que crate publique : parseur borné (MAX_DEPTH), ne panique
jamais (Result, Principe 3 : parsing schema-first, zéro unwrap), fuzz obligatoire
(cargo-fuzz — c’est l’entrée d’un agent externe sur stdin), proptest round-trip
(parse∘serialize = identité). Coverage 100 % (couche 1). Nommage [ADR-029].
4. Contrat du mode JSON agent-safe (défini ici, consommé par la CLI)
air-json fournit la brique ; la CLI air-keystore ([ADR-115]) l’exploitera ainsi :
entrée JSON sur stdin (zéro quoting shell), sortie structurée sur stdout, erreurs
en JSON (stdout + code de sortie ≠ 0), zéro prompt TTY (secret admin par champ/stdin).
Ce contrat est l’interface recommandée pour l’usage programmatique/agent — il contourne tout
parsing shell (anti-injection ticks/backticks).
Conséquences
Positives.
- Réutilisation : un seul codec JSON, fuzzé, partagé (keystore CLI, air-sshd, air-ssh) — fin de la duplication.
- Os-free, deps minimales : pas de
serde_jsonsur*-linux-air; cohérent [ADR-024]/[ADR-088]. - Interface agent-safe : le mode JSON contourne le parsing shell — sûr pour un agent/outil.
- Sûreté prouvée : borné + jamais-panique + fuzz, hérités et durcis.
Négatives / coûts assumés.
- Codec maison à maintenir (vs
serde_json) : périmètre volontairement réduit (entiers, pas de flottants) → coût faible ; extension seulement sur besoin mesuré. - Migration
air-sshd(retrait du module local → dépendanceair-json) : incrément avec non-régression E.6. - Nouvelle crate aux exigences couche 1 (coverage 100 %, fuzz, proptest).
Mise en œuvre (référence, hors décision). Incréments : (a) crate air-json (déplacement de
json.rs + API publique + doc) ; (b) fuzz + proptest round-trip ; (c) migration air-sshd ;
(d) consommation par la CLI air-keystore ([ADR-115]). Non engagés par cet ADR.
Alternatives rejetées
Aucune alternative n’a été consignée lors de l’instruction.