Un conteneur tout neuf dans la CI de lithair, déclaré en quatre lignes dans cidx.toml : une image Rust épinglée par digest, un script qui installe PostgreSQL 17 dedans, génère une PKI de test et lance la suite avec LITHAIR_REQUIRE_POSTGRES=1, et privileged = true parce qu’installer un paquet demande d’écrire dans /var/lib/apt.
Et ça tombe :
E: List directory /var/lib/apt/lists/partial is missing. - Acquire (13: Permission denied)
Comme si le drapeau n’existait pas. privileged = true est bien là, relu trois fois. Le conteneur tourne quand même avec l’utilisateur de l’hôte.
Ce qui se passait
cidx réutilise les conteneurs d’une exécution à l’autre. Pour savoir si le conteneur existant est encore le bon, il calcule une empreinte de la configuration : image, commande, répertoire de travail, point d’entrée, volumes, variables d’environnement. Et un commentaire dans le code, honnête, dit que PullPolicy, Privileged et Timeout sont laissés en dehors de l’empreinte exprès, parce qu’ils « affectent le comportement de l’exécution, pas le conteneur ».
C’est vrai pour le timeout. C’est faux pour privileged. Dans cidx, un conteneur non privilégié tourne avec l’identifiant de l’utilisateur de l’hôte ; un conteneur privilégié tourne en root. Le drapeau change donc User dans la configuration Docker, c’est-à-dire le conteneur lui-même. En passant de false à true, cidx a regardé l’empreinte, l’a trouvée identique, et a repris le conteneur créé avant, avec son utilisateur d’avant.
docker rm -f sur le conteneur règle le problème. Ce n’est pas une solution, c’est un diagnostic : l’issue cidx#531 est ouverte avec la cause et une proposition, inclure dans l’empreinte tout ce qui change la configuration du conteneur créé.
Dix-huit heures
2026-10-02 14:55Z lithair cidx#531 ouverte : "privileged réutilise l'ancien conteneur"
2026-10-03 08:04Z cidx #532 recréer le conteneur quand son utilisateur change
08:27Z cidx #533 comparer l'environnement de création, pas celui déclaré
08:39Z cidx #534 v3.8.0
09:07Z lithair #302 "ci: upgrade cidx to 3.8.0" — un seul diff, le numéro
Dix-huit heures et douze minutes entre l’issue et la version consommée. Le deuxième correctif n’était pas dans l’issue : en regardant l’empreinte, un défaut voisin est apparu. cidx comparait l’environnement déclaré dans la configuration, pas celui avec lequel le conteneur avait été créé. Deux chemins pour la même question, « ce conteneur est-il encore le bon ? », et l’un des deux pouvait répondre oui à tort.
La PR lithair qui consomme la version tient en une phrase de description : « 3.8.0 recrée un conteneur quand privileged change, trouvé en ajoutant le conteneur postgres-test ». Le workflow GitHub est régénéré par cidx generate github, et le seul diff est le numéro de version.
Pas la première fois cette semaine
C’est la quatrième fois en cinq jours que la CI de lithair écrit le cahier des charges de cidx.
- Pas de cache dans le workflow généré (cidx#503) : v3.5.0 ajoute un cache par phase, lithair le consomme en 3.6.0 le 29 septembre.
- Pas d’annulation des runs précédents sur une PR (cidx#504) : même release.
- Le cache repart de la clé précédente quand la clé change (cidx#515). Mesuré sur la phase de test de lithair : cargo ne nettoie jamais
target/, donc chaque changement de clé empilait une génération d’artefacts sur la précédente, 3,3 → 5,4 Gio après un seul bump de version. Correctif en 3.7.0 :cache_restore_fallback = false, et le premier run après un changement de clé compile à froid, ce qui est le prix honnête d’un cache qui ne ment pas sur sa taille. privilegedignoré (cidx#531) : 3.8.0.
Trois montées de version de cidx dans lithair en cinq jours, 3.6.0, 3.7.0, 3.8.0, chacune avec une raison écrite dans la PR. Sept issues ouvertes par le même auteur et fermées dans la semaine côté cidx. C’est le même mécanisme qui a produit sept releases de lithair en août : le consommateur dicte, l’outil suit. Simplement, cette fois, lithair est le consommateur et cidx l’outil.
Pendant ce temps, lithair
Le conteneur Postgres existait parce que lithair venait de gagner son troisième palier de stockage. La RFC 296 pose le vocabulaire avant le code :
palier où aujourd'hui
─────────────────────────────────────────────────────────────────
L1 mémoire du processus modèles natifs (SCC2), toujours résidents
L2 disque local durable journal natif + snapshots ; Turso embarqué
L3 base externe nouveau : PostgreSQL
l'autorité d'un modèle vit dans exactement un palier ;
« durable » se déclare, et exige une autorité L3
lithair-postgres 0.1 arrive en v1.17.0 avec la même surface que l’adaptateur Turso : #[storage(postgres)], les mêmes routes générées, migrations et commandes applicatives. Les choix sont ceux de quelqu’un qui a déjà perdu des données : SERIALIZABLE partout avec un nombre borné de rejeux, un schéma lithair que le framework possède et migre sous verrou consultatif pour que ça n’arrive qu’une fois à travers les nœuds, TLS vérifié quel que soit le sslmode demandé, sockets Unix refusées. Le cœur ne dépend toujours d’aucun pilote.
Le reste de la semaine, dans l’ordre : v1.13.0 branche les modèles déclaratifs sur le consensus natif et corrige le journal frontend qui avait gonflé ce site à 1,3 Go ; v1.14.0 et v1.15.0 ajoutent les commandes applicatives atomiques, natives puis Turso ; v1.16.0 apporte le login OpenID Connect pour les handlers personnalisés, avec PKCE et tentative à usage unique ; v1.18.0 pose un cache L1 sur les modèles externes, avec #[retention(memory, max_mb, ttl)] et invalidation par pg_notify. Trois RFC mergées avant leur code, à chaque fois.
La limite, nommée
Sept releases en sept jours est un sprint, pas une cadence. J’ai écrit la même phrase il y a deux semaines pour cinq releases en deux jours ; elle reste vraie.
Et une erreur que j’ai attrapée ce matin, dans mes propres outils : la réplication des sessions dans le cluster natif a été mergée cinq heures après le tag v1.18.0. Elle est sur main, elle n’est dans aucune version, et le suivi automatique que je maintiens l’avait rangée dans la 1.18. La release suivante dira vrai ; la carte disait faux jusqu’à ce matin.
Enfin, le login OIDC tire rsa 0.9 via openidconnect, et ce crate porte RUSTSEC-2023-0071. C’est écrit dans la note de version, pas caché dans un fichier d’exceptions. Et c’est là que la semaine cidx rejoint la semaine lithair : depuis la 3.7.0, la barrière d’audit de cidx donne à une alerte sept jours à partir de sa première observation avant de faire échouer le build, pas à partir de sa date de publication, et aucune grâce pour une vulnérabilité exploitée connue ni pour une exception expirée. La même semaine, trois CVE publiées le jour même ont reçu leur réponse le jour même. Une barrière qui laisse sept jours pour décider, puis qui casse, c’est la seule façon pour deux dépôts de sortir une version le même matin sans mentir sur une CVE du jour.
Une boucle qui tourne en dix-huit heures, ce n’est pas de la vitesse. C’est un écosystème qui existe.