Skip to content

v0.1.145

Choose a tag to compare

@github-actions github-actions released this 31 Aug 12:05
· 22 commits to main since this release
570f8f5

⚠️ Rien à faire pour un hôte, et c'est le seul message de ce train. Il ne porte que de
l'outillage : dix gardes de CI et leurs bancs, pas une ligne du code servi. Aucune migration, aucun
changement de contrat, aucun changement de comportement. Monter est sans effet visible ; ne pas
monter l'est tout autant.

Il est publié parce que main ne doit pas rester loin de ce que le registre sert, pas parce qu'il
apporte quelque chose à qui l'installe.

⚠️ Ce que ces dix entrées ont en commun, et qui vaut mieux que leur liste. Une seule ajoute une
règle qui manquait. Les neuf autres corrigent la façon de prouver une règle déjà écrite — un
analyseur absent du chemin du rouge, un plancher aveugle aux disparitions, une discrimination qui
tenait au tri alphabétique, cinq gardes qui affirmaient une absence sans pouvoir distinguer « rien
trouvé » de « rien regardé ». Aucune n'était visible depuis la précédente.

Et cinq fois sur ces quatre jours, le remède existait déjà dans ce dépôt — écrit, commenté, et non
appliqué à l'endroit d'à côté. Une garde bien écrite explique son mécanisme, et cette explication
est ce qui rend le fichier crédible : on ne rouvre pas la phrase qui justifie l'outil.

Changed

  • ⚠️ Les trois dernières gardes d'absence portent un témoin — et la mesure a imposé un mécanisme
    différent de celui des deux précédentes.
    secrets-en-clair, renvois-par-position et
    portes-de-reponse affirment chacune une absence sur un large périmètre. Aveuglées, mesuré : les
    trois imprimaient leur résumé complet et sortaient 0.

    ⚠️ La recette des deux gardes précédentes ne marche pas ici, et c'est la garde qui l'a dit. Un
    témoin dérivé — « au moins un corps écrit sur place » — refuse sur un dépôt sain : zéro
    corps reconnu pour onze .end( bruts, parce que tout passe par le module des portes. Il aurait
    exigé la chose même que la garde décourage.

    Un témoin dérivé n'est possible que si la forme correcte est une chose que le dépôt est censé
    CONTENIR.
    Un bloc permissions: et un appel au module crypto le sont ; un secret, un renvoi par
    numéro de ligne et un corps sans type ne le sont pas. Pour ceux-là il faut fabriquer le cas :
    poser un exemplaire fautif, vérifier que la sonde le VOIT, le jeter.

    ⚠️ Ce mécanisme n'était pas neuf ici. L'étape RLS de ci.yml le pratique depuis des semaines
    sur les politiques Postgres — « on pose une politique dont on sait qu'elle existe, on vérifie que
    la sonde la VOIT, et on l'enlève. Sans ce détour, le zéro qui suit ne prouverait rien. » Il
    n'avait jamais été porté jusqu'aux outils. Cinquième fois que le remède existe ici, inutilisé.

    ⚠️ Et le témoin des secrets est assemblé à l'exécution, jamais écrit. Cette garde balaie 104
    fichiers de tools/, le sien compris : un faux identifiant en clair y serait signalé par elle-même,
    on l'exempterait, et l'exemption deviendrait le trou que son propre en-tête décrit. Un banc vérifie
    qu'aucun fichier suivi ne porte ce littéral.

    Pour portes-de-reponse, le témoin distingue voir de juger : une sonde qui reconnaît la
    forme mais ne la juge plus fautive laisse passer exactement ce que la garde attrape, et un témoin
    qui ne vérifierait que « vu » ne le dirait pas.

  • ⚠️ Deux gardes qui affirment une ABSENCE portent désormais le témoin de leur RÈGLE, pas seulement
    celui de leur périmètre.
    L'idée vient de la session STUDIO, qui a trouvé la même chose chez elle
    sur 97 fichiers : notre témoin d'exception prouve qu'une exception a encore un sujet, celui-ci
    prouve que la règle en a encore un. Deux moitiés de la même précaution.

    permissions-workflows affirme « aucune écriture à la racine » sur neuf fichiers ;
    liaison-de-crypto affirme « aucun appel sur le global » sur trente et un. Leur panne la plus
    probable — une sonde qui ne reconnaît plus la forme — produit elle aussi une absence.
    Le plancher
    qui existait compte les FICHIERS LUS, jamais la FORME RECONNUE, et ne peut donc pas les distinguer.

    Mesuré en aveuglant chaque sonde :

    avant : permissions : 9 workflows, aucune écriture à la racine          code 0
    après : GARDE NON CONCLUANTE — aucun bloc « permissions: » reconnu…     code 2
    

    ⚠️ Et la nuance, qui change la sévérité et qu'il serait malhonnête de taire : leurs bancs, eux,
    attrapaient déjà la sonde aveugle.
    Ce n'était donc pas une garde morte — la RÈGLE était protégée.
    C'est le verdict imprimé qui ne l'était pas, et c'est lui qui va dans le journal de la forge et
    sous les yeux de quiconque lance l'outil à la main. La ligne comptait le périmètre et se lisait
    comme une mesure.

    Les deux résumés disent maintenant ce qu'ils ont reconnu : « 9 bloc(s) lu(s) dans 9
    workflow(s) », « 5 fichier(s) appellent le module parmi 31 ». Plancher à un dans les deux cas —
    le compte du jour serait collé au relevé du jour.

    Pour liaison-de-crypto, ce témoin est distinct de celui qui existait déjà :
    methodesDuModuleSeul() refuse quand plus aucune méthode ne sépare le module du global, donc il
    prouve que la question a encore un sens sur ce Node ; le neuf prouve que la sonde sait
    encore lire la réponse. Deux cécités différentes, deux refus différents.

  • ⚠️ Deux bancs choisissaient leur cible par l'ORDRE DE TRI d'un dossier — la dette la plus vieille
    de la série, soldée par construction plutôt que par mesure.
    Elle était signalée depuis trois
    messages sans jamais avoir été cherchée.

    Le balayage n'a trouvé aucune nouvelle instance du défaut, et c'est la mesure qui le dit :
    chaque candidat a été essayé, un par un. Dépouiller n'importe laquelle des deux images node
    rougit ; abaisser n'importe laquelle des dix déclarations littérales rougit. Le vert n'était
    pas un accident.

    Mais « mesuré aujourd'hui » et « ne peut pas dépendre du tri » ne sont pas la même affirmation :
    la première a une date, la seconde n'en a pas. Les deux bancs bouclent donc désormais sur tous
    les candidats. Personne ne relit un banc quand il ajoute un fichier.

    ⚠️ Et le balayage a trouvé autre chose : un banc portait DEUX propriétés dans un seul test, ce
    qui les affaiblissait toutes les deux. « Une étape dépouillée est refusée » vaut pour n'importe
    laquelle — il ne faut donc pas en choisir une. « La mutation discrimine PAR ÉTAPE de PAR FICHIER »
    n'a de sens que sur une cible dont le fichier garde d'autres déclarations — il faut donc en
    choisir une, et délibérément. Fondues, la première héritait d'un choix dont elle n'avait pas
    besoin, et la seconde d'un choix qu'elle ne faisait pas.

    Mesuré : trois de nos dix déclarations littérales vivent seules dans leur fichier, et sur
    celles-là la discrimination ne tient pas. Le tri décidait donc si le banc prouvait sa seconde
    propriété. Les deux sont maintenant séparées, et la seconde choisit sa cible pour ce qu'elle est.

  • ⚠️ Chaque exception écrite doit prouver qu'elle a encore un sujet — une entrée morte est une
    porte ouverte d'avance.
    La distinction vient de la session STUDIO : une liste de ce qu'il faut
    REGARDER cesse de couvrir dès qu'un fichier apparaît ; une liste de ce qui est PERMIS fait rougir
    tout fichier qui n'y est pas.
    Nos deux listes de ce qui est permis étaient donc de la bonne forme
    — mais tenues dans un seul sens.

    FICHIERS_MIT déclare src/bridge.ts hors de l'AGPL. Le banc affirmait que le nom est dans
    l'ensemble
    , jamais que le fichier existe. Le jour où il est renommé, l'entrée survit — et un
    futur fichier à ce chemin exact serait relicencié MIT sans décision, alors que l'en-tête de la
    liste dit qu'ajouter un fichier ici « se discute dans une PR, pas dans un correctif de garde ». Le
    relicenciement se ferait par omission, sur une frontière de licence, garde verte.

    INTERNES_TOLERES dit « tout nouveau venu doit être décidé plutôt que découvert ». Cela n'était
    tenu que contre les arrivants : un symbole qui cesse d'être exporté laissait son entrée
    derrière lui, et son retour aurait été toléré au lieu d'être décidé.

    ⚠️ Le remède existait déjà chez nous, inutilisé. La garde qui vérifie qu'aucune autre ne
    déclare victoire sur zéro porte exactement ce patron depuis des semaines : chaque exemption a sa
    raison et une fonction qui rougit quand le motif disparaît. Mécanisme inventé, commentaire
    écrit, et non appliqué aux deux endroits où il manquait.

    ⚠️ Et les éprouvettes ont corrigé la conception. Le contrôle des licences était d'abord dans
    l'outil ; deux bancs ont rougi aussitôt, à juste titre — garde(racine) s'applique à une racine
    QUELCONQUE et les éprouvettes lui passent des dépôts temporaires, tandis que FICHIERS_MIT est
    une constante du VRAI dépôt. Il accusait chaque éprouvette de ne pas contenir src/bridge.ts. Un
    contrôle d'exception appartient là où le SUJET est connu : dans le banc pour les licences, dans
    l'outil pour la surface publique, dont le sujet est toujours le module réel.

  • ⚠️ Le périmètre d'images-epinglees vient du disque — c'était une liste écrite, dans la garde où
    ça coûtait le plus cher.
    ci.yml lui passait Dockerfile .zap/Dockerfile en dur. Cette garde
    est la règle qui empêche une image de changer sous nos pieds : le jour où quelqu'un ajoutait un
    troisième Dockerfile, elle aurait rendu « toutes épinglées » en n'ayant regardé que deux fichiers
    sur trois. Son refus « zéro image » ne l'aurait pas dit — il compte ce qu'il a LU, il ne sait pas
    ce qu'il n'a pas OUVERT
    .

    Le périmètre est désormais git ls-files, partagé avec node-de-l-image plutôt que dupliqué :
    deux exemplaires de « quels Dockerfiles existe-t-il ? » divergeraient. Un banc interdit à un
    workflow de le remplacer par des arguments — dériver un périmètre ne sert à rien si un appel
    l'écrase.

    ⚠️ Et ce lot a réintroduit, en le déplaçant, le défaut que resultat-garde existe pour
    interdire.
    Le calcul du périmètre vivait AU-DESSUS de tenter, ce qui était sans conséquence
    tant qu'il valait ["Dockerfile"], une constante. Devenu une lecture du disque, son exception
    a cessé d'être rattrapée : hors d'un dépôt git, l'outil mourait sur une trace de pile et sortait
    1 — « corrige ta branche » pour un environnement sans git, exactement le rouge que la taxonomie
    sépare. Les quatre mutants de ce lot et ses cinquante-trois bancs étaient verts ; seule
    planchers-des-gardes l'a dit
    , en lançant l'outil dans un dépôt vide. Le calcul est passé
    dedans, et la frontière est maintenant commentée à l'endroit où on la franchit.

Added

  • ⚠️ La seconde moitié de la paire : ce que l'IMAGE embarque, confronté à engines. La première
    garde le confronte à ce que la forge installe ; rien ne le confrontait à ce qu'un
    auto-hébergeur exécute. Des deux exemplaires non mesurés, celui-ci était le plus proche de la
    production.

    ⚠️ Trois gardes touchent déjà ce fichier, et aucune ne pose cette question — relevé plutôt que
    supposé : images-epinglees demande « l'image vient-elle d'un condensat ? » ; ecartMajeur
    demande « l'étiquette dit-elle vrai sur le condensat ? » ; le job docker construit l'image et
    lui demande sa version. Le jour où engines passera au-delà de 24, les trois resteront
    vertes
    : l'image serait épinglée, son étiquette dirait vrai, et elle embarquerait un moteur que
    notre propre paquet déclare non supporté. Deux exemplaires cohérents entre eux ne disent rien du
    troisième fait qu'ils ignorent.

    tools/node-de-l-image.mjs prend son périmètre sur le disque (git ls-files, jamais une liste
    écrite), importe le lecteur de Dockerfile existant plutôt que d'en écrire un quatrième — celui
    de ce dépôt a déjà été aveugle une fois — et porte la même relation que sa jumelle : toute image
    qui fournit node doit dire laquelle. FROM node@sha256:… sans étiquette est valide, épinglé,
    et sa version est indéterminable ; un comptage y verrait une image de plus, la relation y voit une
    dette. Deux absences restent distinctes : rien de déclaré est une violation, une étiquette que
    semver ne lit pas est non concluant.

    ⚠️ Et ce que le resserrement écarte est RENDU. Dockerfile.prod en est un, Dockerfile.md est
    une page qui en parle, et aucune lecture du nom ne les sépare sans convention. La convention est
    donc écrite — les extensions de document sont écartées — et les fichiers écartés sont dits en
    avertissement
    , parce qu'un resserrement muet est ce qui a coûté trois lecteurs à ce dépôt.

  • ⚠️ La relation est PAR ÉTAPE, et le banc cesse d'en avoir la preuve par accident. La session
    STUDIO est tombée le 31/08 dans un trou que son dépôt lui cachait : sa garde comptait des
    fichiers là où il faut compter des étapes, et comme aucun de ses fichiers ne portait deux
    setup-node, les deux formulations y étaient observationnellement identiques. Aucune mutation
    ne pouvait les distinguer — ses quatre mutants mouraient tous correctement, en éprouvant la règle
    écrite plutôt que la règle voulue.

    Vérifié chez nous plutôt que supposé : notre règle est par étape (deux étapes dont une muette
    → 2 installations, 1 refus). Le trou n'est pas dans la règle. Il est dans le banc.

    Notre mutation sur fichier réel vise declarations.find(…), c'est-à-dire l'ordre de tri du
    dossier
    : elle ne séparait « par étape » de « par fichier » que parce que cette cible tombe dans
    ci.yml, qui porte quatre étapes. Si elle avait trié dans cla.yml — une seule étape — les deux
    règles auraient rendu exactement le même vert. Une discrimination qui dépend du nom des fichiers
    est un vert juste pour une mauvaise raison.

    Le cas est donc fabriqué : deux étapes, une seule déclaration, et le banc affirme qu'une règle
    par fichier serait verte là où la nôtre refuse. La mutation réelle, elle, dit désormais ce
    qu'elle discrimine au lieu de le supposer. S'y ajoute le défaut inverse, que le STUDIO a écrit pour
    nous faute d'avoir des matrices : une étape qui tient sa version de ${{ matrix.node }} déclare
    — la compter muette accuserait le dépôt sain, et une garde qui crie faux finit desserrée.

  • ⚠️ La garde de node porte une RELATION, plus seulement des planchers — et c'est le correctif d'un
    autre dépôt qui a désigné le trou chez nous.
    La session STUDIO a trouvé, dans sa propre garde, des
    planchers collés au relevé du jour, et les a remplacés par un périmètre dérivé du disque. Passée
    sur nos fichiers, l'idée a mesuré ceci : 11 étapes actions/setup-node, 11 déclarant une version,
    et rien qui garde ce rapport.

    Un plancher compte ce qu'il voit ; il ne sait pas ce qui aurait dû être là. Retirer l'entrée
    node-version d'une étape faisait passer le relevé de 12 à 11 déclarations — au-dessus du plancher
    de 8 — et la garde restait verte pendant que la forge installait le défaut de l'action, que rien
    n'épingle et que rien ne confronte à engines. La lecture sautait d'ailleurs toute étape sans bloc
    with:, donc la forme la plus dépouillée était aussi la plus invisible.

    La règle est donc : toute étape qui installe node déclare laquelle, périmètre pris sur le disque.
    Elle ne remplace pas les planchers, elle les complète — un plancher garde la SONDE (« ai-je lu le
    dossier ? »), la relation garde le SUJET (« chaque site d'installation est-il déclaré ? ») — et elle
    porte le sien, parce qu'une relation peut être satisfaite à vide : zéro installation relevée,
    aucune manquante, règle vraie pour n'avoir rien regardé.

    ⚠️ Ce plancher-là s'est justifié le jour même, chez son auteur : garde() déstructurait encore
    deux champs et ne transmettait pas le relevé neuf au verdict. Rien d'autre ne l'aurait dit.

    ⚠️ Et l'ordre des contrôles est une décision, pas une mise en page. La relation est évaluée
    APRÈS les planchers de déclarations : si la lecture des node-version cassait, les onze
    installations paraîtraient toutes muettes et la garde rendrait onze violations — « corrige ta
    branche » pour une panne qui n'y est pas. Un banc éprouve cet ordre, et le mutant qui l'inverse ne
    fait rougir que lui.

  • ⚠️ La garde de node est éprouvée dans les DEUX sens — et le second attrape ce qu'aucun des vingt-trois
    autres bancs ne voyait.
    L'idée vient de la session STUDIO, qui a retiré une exception de sa propre
    garde le 31/08 et a nommé ce qui manque à une mutation unique : « sans cette seconde mutation,
    j'aurais eu une suite verte parfaitement compatible avec j'ai supprimé un test gênant ».

    Ce qui manquait ici était plus précis que « un sens sur deux ». Toutes nos violations étaient
    éprouvées sur des déclarations fabriquées, passées directement au verdict : l'analyseur n'était
    jamais dans le chemin du rouge, seulement dans celui du vert. Le sens neuf part donc du texte
    d'un vrai workflow
    — une version littérale du dépôt abaissée à node 18, relue par le vrai
    analyseur, refusée en nommant le vrai fichier.

    Mesuré : un analyseur muté pour ignorer le texte qu'on lui passe et relire le disque fait
    rougir ces deux bancs neufs et eux seuls — les vingt-trois autres restent verts. C'est la
    preuve que le trou existait.

    S'ajoutent la partition énoncée d'un seul tenant — vert au milieu, rouge des deux bords, donc
    aucun vert atteignable par accident — et un banc qui vérifie qu'un workflow lance vraiment cette
    garde. Ce dernier ne protège pas la règle : le banc lit .github/workflows pour de vrai, donc
    retirer l'étape de ci.yml la ferait rougir quand même — vérifié, contre une première rédaction qui
    affirmait le contraire. Il protège la seconde jambe : la règle est tenue à deux endroits, c'est
    délibéré, et une redondance non énoncée est précisément ce qui a fait survivre un mutant dans ce
    même fichier.

  • ⚠️ La CI mesure enfin la version de node qu'elle installe — la garde voisine avait écrit ce
    diagnostic le 25/08 sans se l'appliquer.
    L'en-tête de tools/plancher-de-node.mjs dit :
    « node-version: "22" résout au DERNIER 22.x : la CI atterrit toujours au-dessus du plancher,
    quel qu'il soit. Une règle que l'environnement de vérification satisfait par construction n'est
    pas vérifiée — elle est supposée. » Cette phrase nomme deux paires ; l'outil n'en mesurait
    qu'une, engines contre les dépendances de production. L'autre — engines contre ce que les
    workflows demandent d'installer — restait sur la prose qui venait de la diagnostiquer. Vérifié
    plutôt que supposé : dans tout tools/, la chaîne node-version n'apparaissait qu'une fois,
    et c'était dans ce commentaire.

    tools/node-des-workflows.mjs énumère les workflows depuis le disque — une liste écrite
    cesse de couvrir dès qu'on ajoute un fichier — lit chaque node-version:, résout
    ${{ matrix.node }} et les entrées d'include, et refuse toute version qu'engines n'admet
    pas. Relevé du jour : 12 déclarations dans 5 fichiers, toutes admises. Le rouge qu'elle attend
    n'existe pas encore : il arrivera le jour où le plancher passera au-delà de 22, et ce jour-là
    check (22) validerait en vert sur un moteur que notre propre paquet déclare non supporté.

    La comparaison est intersects, pas subset, et la différence est le cœur de la garde :
    subset("22", ">=22.13.0") est faux — 22.0.0 est dans « 22 » sans être dans engines — donc
    une garde bâtie dessus refuserait la CI d'aujourd'hui, qui est saine. intersects est exact ici
    sous une condition que la garde vérifie au lieu de la supposer : un engines sans plafond.
    Sous un engines borné en haut, elle refuse de conclure plutôt que de rendre un vert dont elle
    ne sait plus ce qu'il vaut. Deux planchers anti-vacuité — sur le nombre de déclarations et
    sur le nombre de fichiers, parce qu'un plancher unique laisse passer un balayage qui garde le
    compte en perdant la moitié du dossier. Ce qu'elle ne sait pas lire (node-version-file,
    lts/*, une expression non résolue) est dit, jamais sauté.

What changed in the package, by zone — 0.1.1440.1.145

Measured on the two tarballs, by the release workflow. Not written by hand.

Zone What it is Added Removed Changed
documents what a human reads 0 0 0
manifest package.json — version, exports, dependencies 0 0 1
server the code the host executes 0 0 0
context the injected-context implementations 0 0 0
browser-types the declarations the host's tsc reads for « ./bridge » — breaks a build, never a page 0 0 0
browser what the visitors' page executes 0 0 0
cli the command-line entry point 0 0 0
types declarations for the server and context entry points — breaks a build, never runtime 0 0 0
database the schema and the migrations the host applies itself 0 0 0
The files themselves
~ package.json