Dans le fichier de conception de stela, une ligne est restée marquée « non vérifié » pendant un mois :
Que le store d’événements de lithair écrive de façon synchrone. S’il bufferise, la reconstruction du site relit un journal auquel il manque le billet qu’on vient d’écrire — et rend des pages périmées, silencieusement.
Toute l’architecture repose sur cette ligne. Et on ne peut pas la trancher en lisant le code : il faut construire le produit, POSTer un billet, POSTer une reconstruction, et regarder si la page est là.
Elle est là. Il flushe. C’est la PR #2 de stela, et c’est à peu près tout ce que raconte cet article : ce qu’on apprend d’un framework le jour où on construit vraiment quelque chose dessus.
Ce que c’est
Une stèle est une pierre dressée qui porte une inscription publique — des lois, des poèmes, des annonces. Le blog de l’Antiquité. stela est le moderne : un moteur de blog qui tient en un binaire.
stela new myblog
stela serve
Pas de base externe, pas de Node à l’exécution, pas d’outil de build à côté. Le contenu vit dans un store événementiel sur disque, servi depuis la mémoire par lithair. Quand on publie, les pages touchées sont rendues dans le binaire (Tera + Markdown) et poussées directement en mémoire — pas de générateur, pas de pipeline, pas de redémarrage. Un lecteur reçoit du HTML fini ; une vue de page n’attend jamais un moteur de templates.
Un thème est un dossier de templates Tera. Toute surface d’administration — l’éditeur, la reconstruction, le tableau de bord, le login — pend à un préfixe aléatoire par installation, imprimé une fois à la création : un scanner qui déroule sa liste /wp-admin ne trouve rien contre quoi s’authentifier. Et l’image Docker est FROM scratch : le binaire statique et rien d’autre, donc pas de shell, pas de distribution dont suivre les CVE.
C’est aussi l’idée que je notais ici en juin — « une sorte de WordPress, mais en lithair ». Elle a un nom maintenant.
Le produit qui tire le framework
Voici ce qui s’est passé quand j’ai commencé à écrire ça pour de vrai. lithair était en v1.3.0. Cinq jours plus tard, il était en v1.9.0.
Pas par ambition. Chaque version répond à un manque que stela venait de rencontrer :
stela a besoin de… lithair livre
──────────────────────────────────────────────────────────────────────
réagir à une écriture pour reconstruire v1.8.0 on_mutation — un hook natif,
la page, sans route de rebuild à la main même canal que le SSE
sortir le login de /auth, l'adresse que v1.8.0 with_auth_path — la route se
tout scanner essaie en premier déplace où on veut
un login qui marche depuis un navigateur v1.9.0 le login pose un cookie de
(cookie, pas seulement un Bearer) session, le garde l'accepte,
le logout l'efface
ne pas se faire rejouer une mutation (post-1.9.0) rejet des requêtes
depuis un site tiers cross-site sur cookie
La note de release de la v1.8.0 le dit sans détour : « The consumer-driven release: everything here came from building a real site on Lithair. » Et sur le /auth par défaut, la même note l’appelle par son nom — « un oracle non authentifié pour les listes de mots, la forme /wp-admin ». Personne ne l’aurait écrit comme ça en concevant le framework à vide. On l’écrit quand on vient de voir sa propre page de login à l’endroit exact où un robot la cherche.
La v1.9.0 est plus instructive encore. Faire marcher le login depuis un navigateur a exposé la couche de sessions comme « un tas de fragments : sept autorités pour le nom du cookie, des options de configuration lues mais jamais appliquées, une tâche de nettoyage qui ne tournait jamais sur le chemin RBAC ». Sept endroits décidaient du nom du cookie. Ça marchait tant que personne ne combinait deux chemins. Le premier vrai consommateur les a combinés.
C’est le même motif qu’ailleurs cet été : des options qui existaient sans être lues. La note conclut : « every session/cookie option now does what its documentation says ». Il a fallu un produit pour que ce soit vrai.
Ce que lire ne trouve pas
Ces choses-là n’apparaissent pas en relisant un framework. Elles apparaissent quand on l’utilise sur quelque chose qui a des utilisateurs, des URL, des flux.
- Un blog n’a pas d’extensions dans ses URL. Le serveur de fichiers de lithair déduit le
Content-Typede l’extension du chemin./posts/mon-billetn’en a pas — chaque page serait donc partie enapplication/octet-stream, et le navigateur l’aurait téléchargée au lieu de l’afficher. stela sert ses pages par sa propre route, et le manque est remonté en amont (lithair #193, fermé en v1.4.0). - Un slug est une entrée non fiable. Il arrive par l’API et sert à construire un chemin. Un billet dont le slug est
../../evilécrivait hors de/posts/et atterrissait tel quel dans le flux RSS. Trouvé en lançant le serveur, pas en le lisant ; refusé maintenant au seul endroit par où passe chaque page rendue. - Un flux RSS est du XML, et les URL contiennent des
/. L’échappement XML de Tera transforme/en/: chaque lien du flux devenaithttp://…— du XML valide, un lien inutilisable. - Une page et le flux partagent le même espace de noms. Une page dont le slug est
rss.xmlremplacerait le flux pour tous les abonnés — ou/index.htmllui-même. Le filtre écrit contre la traversée de chemins se trouve empêcher ça aussi, et un test le vérifie désormais pour qu’on ne le « relaxe » jamais. delete_assetrépondait « succès » sans rien supprimer. Rapporté sur lithair (#227) le mois suivant. Le même défaut de fond que partout ailleurs : un succès annoncé alors que le travail n’a pas eu lieu.
Aucun de ces cinq points n’est une question de framework. Ce sont des questions de blog. Le framework ne pouvait pas les poser tout seul.
Le dogfooding, jusqu’au bout
stela est aussi le premier projet où les trois autres travaillent ensemble sur du réel. La règle est écrite dans son dépôt : cidx possède la qualité du code, probatum possède le comportement, et un contrôle appartient à exactement l’un des deux.
La première page rendue a été écrite à l’envers : six contrôles probatum posés rouges dans un commit, puis rendus verts dans le suivant.
✓ serve starts and answers (ready in 0.2s)
✓ publishing a post is accepted (HTTP 201)
✓ rebuild is accepted (HTTP 200)
✓ the published post is on the index (HTTP 200)
✓ the post has its own page, markdown rendered (HTTP 200)
✓ the feed lists it (HTTP 200)
Et dans l’autre sens, stela renvoie ce qu’il trouve : depuis ce petit dépôt, onze issues et quatre PRs ont été filées en amont sur lithair, cidx et probatum. Dix sont fermées. Quand la première mouture de stela new a été bloquée par une lacune d’isolation dans cidx, l’issue est partie là-bas, cidx 3.3 l’a corrigée, et la tranche s’est débloquée.
Un produit est le meilleur rapport de bug qu’un outillage puisse recevoir. Il ne dit pas « ça devrait marcher » ; il dit « voilà ce que j’ai essayé de faire ».
Où ça en est, honnêtement
Le README de stela porte l’avertissement en tête, et je le reprends tel quel : ça marche, et ce n’est pas publié. On peut scaffolder un blog, se connecter, écrire, publier. Ce qui n’est pas fait, c’est le livrer : 0.0.1 sur crates.io est une réservation de nom, aucune image n’est publiée, aucune version n’est taguée. Aujourd’hui c’est cargo build ou docker build.
La dernière pièce du périmètre minimal — les pages hors flux, /about et non /posts/about — est dans une PR encore ouverte. Et je n’y ai pas touché depuis fin août ; l’agent qui surveille mes dépôts a compté zéro commit cette semaine, et il a raison.
Côté lithair, la protection cross-site est sur main depuis dix-huit jours sans tag. Il y a une v1.10.0 qui attend une décision, pas du code — vous connaissez la chanson.
Et la destination : ce site-ci. arcker.org tourne aujourd’hui sur un binaire lithair sur mesure. Il migrera sur stela. C’est même toute la raison de faire un produit plutôt qu’un site de plus : la prochaine fois que le framework manque de quelque chose, je le saurai parce que mon propre blog me le dira.