eqs openapi export lit le dist/ sans vérifier qu'il est à jour : on peut committer un contrat périmé, et --check ne le voit pas #33
Closed
opened 2026-09-09 16:45:06 +00:00 by nicolas-fryder
·
2 comments
No Branch/Tag specified
main
dev
@eqs/tooling@0.24.3
@eqs/testing@0.24.3
@eqs/telemetry@0.24.3
@eqs/platform-next@0.24.3
@eqs/platform-nest@0.24.3
@eqs/http@0.24.3
@eqs/errors@0.24.3
@eqs/config@0.24.3
@eqs/cli@0.24.3
@eqs/auth@0.24.3
@eqs/tooling@0.24.2
@eqs/testing@0.24.2
@eqs/telemetry@0.24.2
@eqs/platform-next@0.24.2
@eqs/platform-nest@0.24.2
@eqs/http@0.24.2
@eqs/errors@0.24.2
@eqs/config@0.24.2
@eqs/cli@0.24.2
@eqs/auth@0.24.2
@eqs/tooling@0.24.1
@eqs/testing@0.24.1
@eqs/telemetry@0.24.1
@eqs/platform-next@0.24.1
@eqs/platform-nest@0.24.1
@eqs/http@0.24.1
@eqs/errors@0.24.1
@eqs/config@0.24.1
@eqs/cli@0.24.1
@eqs/auth@0.24.1
@eqs/tooling@0.24.0
@eqs/testing@0.24.0
@eqs/telemetry@0.24.0
@eqs/platform-next@0.24.0
@eqs/platform-nest@0.24.0
@eqs/http@0.24.0
@eqs/errors@0.24.0
@eqs/config@0.24.0
@eqs/cli@0.24.0
@eqs/auth@0.24.0
@eqs/tooling@0.23.0
@eqs/testing@0.23.0
@eqs/telemetry@0.23.0
@eqs/platform-next@0.23.0
@eqs/platform-nest@0.23.0
@eqs/http@0.23.0
@eqs/errors@0.23.0
@eqs/config@0.23.0
@eqs/cli@0.23.0
@eqs/auth@0.23.0
@eqs/tooling@0.22.0
@eqs/testing@0.22.0
@eqs/telemetry@0.22.0
@eqs/platform-next@0.22.0
@eqs/platform-nest@0.22.0
@eqs/http@0.22.0
@eqs/errors@0.22.0
@eqs/config@0.22.0
@eqs/cli@0.22.0
@eqs/auth@0.22.0
@eqs/tooling@0.21.0
@eqs/testing@0.21.0
@eqs/telemetry@0.21.0
@eqs/platform-next@0.21.0
@eqs/platform-nest@0.21.0
@eqs/http@0.21.0
@eqs/errors@0.21.0
@eqs/config@0.21.0
@eqs/cli@0.21.0
@eqs/auth@0.21.0
@eqs/tooling@0.20.0
@eqs/testing@0.20.0
@eqs/telemetry@0.20.0
@eqs/platform-next@0.20.0
@eqs/platform-nest@0.20.0
@eqs/http@0.20.0
@eqs/errors@0.20.0
@eqs/config@0.20.0
@eqs/cli@0.20.0
@eqs/auth@0.20.0
@eqs/tooling@0.19.0
@eqs/testing@0.19.0
@eqs/telemetry@0.19.0
@eqs/platform-next@0.19.0
@eqs/platform-nest@0.19.0
@eqs/http@0.19.0
@eqs/errors@0.19.0
@eqs/config@0.19.0
@eqs/cli@0.19.0
@eqs/auth@0.19.0
@eqs/tooling@0.17.1
@eqs/testing@0.17.1
@eqs/telemetry@0.17.1
@eqs/platform-next@0.17.1
@eqs/platform-nest@0.17.1
@eqs/http@0.17.1
@eqs/errors@0.17.1
@eqs/config@0.17.1
@eqs/cli@0.17.1
@eqs/auth@0.17.1
@eqs/tooling@0.17.0
@eqs/testing@0.17.0
@eqs/telemetry@0.17.0
@eqs/platform-next@0.17.0
@eqs/platform-nest@0.17.0
@eqs/http@0.17.0
@eqs/errors@0.17.0
@eqs/config@0.17.0
@eqs/cli@0.17.0
@eqs/auth@0.17.0
@eqs/tooling@0.16.0
@eqs/testing@0.16.0
@eqs/telemetry@0.16.0
@eqs/platform-next@0.16.0
@eqs/platform-nest@0.16.0
@eqs/http@0.16.0
@eqs/errors@0.16.0
@eqs/config@0.16.0
@eqs/cli@0.16.0
@eqs/auth@0.16.0
@eqs/tooling@0.15.0
@eqs/testing@0.15.0
@eqs/telemetry@0.15.0
@eqs/platform-next@0.15.0
@eqs/platform-nest@0.15.0
@eqs/http@0.15.0
@eqs/errors@0.15.0
@eqs/config@0.15.0
@eqs/cli@0.15.0
@eqs/auth@0.15.0
@eqs/tooling@0.14.0
@eqs/testing@0.14.0
@eqs/telemetry@0.14.0
@eqs/platform-next@0.14.0
@eqs/platform-nest@0.14.0
@eqs/http@0.14.0
@eqs/errors@0.14.0
@eqs/config@0.14.0
@eqs/cli@0.14.0
@eqs/auth@0.14.0
@eqs/tooling@0.13.0
@eqs/testing@0.13.0
@eqs/telemetry@0.13.0
@eqs/platform-next@0.13.0
@eqs/platform-nest@0.13.0
@eqs/http@0.13.0
@eqs/errors@0.13.0
@eqs/config@0.13.0
@eqs/cli@0.13.0
@eqs/auth@0.13.0
@eqs/tooling@0.12.2
@eqs/testing@0.12.2
@eqs/telemetry@0.12.2
@eqs/platform-next@0.12.2
@eqs/platform-nest@0.12.2
@eqs/http@0.12.2
@eqs/errors@0.12.2
@eqs/config@0.12.2
@eqs/cli@0.12.2
@eqs/auth@0.12.2
@eqs/tooling@0.12.1
@eqs/testing@0.12.1
@eqs/telemetry@0.12.1
@eqs/platform-next@0.12.1
@eqs/platform-nest@0.12.1
@eqs/http@0.12.1
@eqs/errors@0.12.1
@eqs/config@0.12.1
@eqs/cli@0.12.1
@eqs/auth@0.12.1
@eqs/tooling@0.12.0
@eqs/testing@0.12.0
@eqs/telemetry@0.12.0
@eqs/platform-next@0.12.0
@eqs/platform-nest@0.12.0
@eqs/http@0.12.0
@eqs/errors@0.12.0
@eqs/config@0.12.0
@eqs/cli@0.12.0
@eqs/auth@0.12.0
@eqs/tooling@0.11.2
@eqs/testing@0.11.2
@eqs/telemetry@0.11.2
@eqs/platform-next@0.11.2
@eqs/platform-nest@0.11.2
@eqs/http@0.11.2
@eqs/errors@0.11.2
@eqs/config@0.11.2
@eqs/cli@0.11.2
@eqs/auth@0.11.2
@eqs/tooling@0.11.1
@eqs/testing@0.11.1
@eqs/telemetry@0.11.1
@eqs/platform-next@0.11.1
@eqs/platform-nest@0.11.1
@eqs/http@0.11.1
@eqs/errors@0.11.1
@eqs/config@0.11.1
@eqs/cli@0.11.1
@eqs/auth@0.11.1
@eqs/tooling@0.11.0
@eqs/testing@0.11.0
@eqs/telemetry@0.11.0
@eqs/platform-next@0.11.0
@eqs/platform-nest@0.11.0
@eqs/http@0.11.0
@eqs/errors@0.11.0
@eqs/config@0.11.0
@eqs/cli@0.11.0
@eqs/auth@0.11.0
@eqs/tooling@0.10.1
@eqs/testing@0.10.1
@eqs/telemetry@0.10.1
@eqs/platform-next@0.10.1
@eqs/platform-nest@0.10.1
@eqs/http@0.10.1
@eqs/errors@0.10.1
@eqs/config@0.10.1
@eqs/cli@0.10.1
@eqs/auth@0.10.1
@eqs/tooling@0.10.0
@eqs/testing@0.10.0
@eqs/telemetry@0.10.0
@eqs/platform-next@0.10.0
@eqs/platform-nest@0.10.0
@eqs/http@0.10.0
@eqs/errors@0.10.0
@eqs/config@0.10.0
@eqs/cli@0.10.0
@eqs/auth@0.10.0
@eqs/tooling@0.9.0
@eqs/testing@0.9.0
@eqs/telemetry@0.9.0
@eqs/platform-next@0.9.0
@eqs/platform-nest@0.9.0
@eqs/http@0.9.0
@eqs/errors@0.9.0
@eqs/config@0.9.0
@eqs/cli@0.9.0
@eqs/auth@0.9.0
@eqs/tooling@0.8.1
@eqs/testing@0.8.1
@eqs/telemetry@0.8.1
@eqs/platform-next@0.8.1
@eqs/platform-nest@0.8.1
@eqs/http@0.8.1
@eqs/errors@0.8.1
@eqs/config@0.8.1
@eqs/cli@0.8.1
@eqs/auth@0.8.1
@eqs/tooling@0.8.0
@eqs/testing@0.8.0
@eqs/telemetry@0.8.0
@eqs/platform-next@0.8.0
@eqs/platform-nest@0.8.0
@eqs/http@0.8.0
@eqs/errors@0.8.0
@eqs/config@0.8.0
@eqs/cli@0.8.0
@eqs/auth@0.8.0
@eqs/tooling@0.7.0
@eqs/testing@0.7.0
@eqs/telemetry@0.7.0
@eqs/platform-next@0.7.0
@eqs/platform-nest@0.7.0
@eqs/http@0.7.0
@eqs/errors@0.7.0
@eqs/config@0.7.0
@eqs/cli@0.7.0
@eqs/auth@0.7.0
@eqs/tooling@0.6.0
@eqs/testing@0.6.0
@eqs/telemetry@0.6.0
@eqs/platform-next@0.6.0
@eqs/platform-nest@0.6.0
@eqs/http@0.6.0
@eqs/errors@0.6.0
@eqs/config@0.6.0
@eqs/cli@0.6.0
@eqs/auth@0.6.0
@eqs/tooling@0.5.1
@eqs/testing@0.5.1
@eqs/telemetry@0.5.1
@eqs/platform-next@0.5.1
@eqs/platform-nest@0.5.1
@eqs/http@0.5.1
@eqs/errors@0.5.1
@eqs/config@0.5.1
@eqs/cli@0.5.1
@eqs/auth@0.5.1
@eqs/tooling@0.5.0
@eqs/testing@0.5.0
@eqs/telemetry@0.5.0
@eqs/platform-next@0.5.0
@eqs/platform-nest@0.5.0
@eqs/http@0.5.0
@eqs/errors@0.5.0
@eqs/config@0.5.0
@eqs/cli@0.5.0
@eqs/auth@0.5.0
@eqs/tooling@0.4.0
@eqs/testing@0.4.0
@eqs/telemetry@0.4.0
@eqs/platform-next@0.4.0
@eqs/platform-nest@0.4.0
@eqs/http@0.4.0
@eqs/errors@0.4.0
@eqs/config@0.4.0
@eqs/cli@0.4.0
@eqs/auth@0.4.0
@eqs/tooling@0.2.0
@eqs/telemetry@0.2.0
@eqs/errors@0.2.0
@eqs/config@0.2.0
@eqs/tooling@0.1.0
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
Infrastructure/eqs-platform#33
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Constaté le 2026-09-09 sur le premier satellite réel monté depuis
eqs-satellite-template, en ajoutant une deuxième ressource à l'API.@eqs/*0.7.0, pnpm 11.8.0.eqs openapi exportlit le point d'entrée déclaré danspackage.json("eqs": { "openapi": "./apps/api/dist/openapi.js" }) — c'est-à-dire le build compilé, pas les sources. Rien ne vérifie que cedist/correspond au code actuel, et rien ne le signale.Ce qui s'est passé
Après avoir écrit un nouveau contrôleur,
pnpm openapia régénéré unopenapi.jsonsans les nouvelles routes — et même sans certaines routes plus anciennes, ledist/datant d'un état antérieur. La commande sort en 0 : aucun avertissement, aucun indice que le contrat produit décrit un code qui n'existe plus.Il a fallu jouer
pnpm --filter api buildavantpnpm openapipour obtenir le bon contrat. Cette étape n'est mentionnée ni dansAGENTS.md, ni dans leREADMEdu gabarit, ni par la commande elle-même.Reproduction
Dans un satellite, à partir d'un état où
dist/est à jour :Pourquoi c'est plus grave qu'un simple oubli d'étape
Le garde-fou censé rattraper ça ne peut pas le faire.
eqs openapi export --checkcompare le contrat committé à ce que produit le mêmedist/. Sidist/est périmé, les deux sont cohérents entre eux et faux tous les deux par rapport aux sources : le contrôle passe au vert.Autrement dit, la chaîne peut committer et déployer un contrat qui ne décrit pas le service, sans qu'aucune porte ne s'en aperçoive — ni le
pre-push, ni la CI. Or le contrat n'est pas un document : c'est ce dont les clients sont générés, et ce que leDockerfilecopie dans l'image.Le symptôme visible pour la personne, en attendant, est déroutant : elle vient d'écrire une route,
pnpm openapine produit rien, et il n'y a aucune raison apparente. On cherche l'erreur dans le décorateur.Ce que ça coûte
Le geste manquant coûte quelques minutes. Le trou dans le contrôle, lui, peut coûter un client généré faux et un contrat déployé qui ment sur le service. C'est ce second point qui motive le ticket.
Ce que je proposerais
Trois pistes, de la plus simple à la plus sûre :
eqs openapi exportconstruit d'abord, ou refuse de tourner si ledist/est plus ancien que les sources (comparaison de dates la plus récente soussrc/contre celle du point d'entrée). Le message dirait quoi faire."openapi": "pnpm --filter api build && eqs openapi export". Corrige l'usage courant, mais laisse le trou de--checkouvert si la CI appelle la CLI directement.--checkconstruit systématiquement avant de comparer. C'est ce qui ferme réellement la faille : le contrôle ne peut plus se comparer à lui-même.La 1 et la 3 me semblent les bonnes ; je n'ai pas la vue d'ensemble de la CLI pour trancher.
Contournement en place
pnpm --filter api buildjoué avant chaquepnpm openapidans le satellite. Rien d'écrit dans le dépôt pour l'instant — c'est une consigne portée à la main, donc exactement le genre de chose qui se perdra.Vrai, reproduit, corrigé — avec une réserve sur un point du ticket, qui est faux et qui compte.
La reproduction, dans un clone du gabarit,
distfraîchement construit :Exactement ce que vous décrivez : la commande sort en 0, le contrat ignore la route, et
--checkconfirme un contrat qui ment. Votre analyse du trou est juste —--checkse compare au mêmedist, donc les deux sont cohérents entre eux et faux tous les deux.Ce qui est faux : « ni le
pre-push, ni la CI ». La CI l'attrape. Le workflowcontract(eqs-workflows/.forgejo/workflows/contract.yml, jobcontrat-a-jour) fait, sur un checkout neuf :Le
disty est reconstruit depuis les sources : le contrôle ne peut pas se comparer à un build périmé. Un contrat en retard rougit en CI.La nuance n'est pas de la coquetterie, parce qu'elle déplace le vrai reproche. Ce n'est pas « personne ne le voit » — c'est « on le voit trop tard » : la CI ne bloque rien par conception (D-013), le webhook Forgejo → Coolify a déjà déployé quand le pipeline démarre. Le contrat faux part en production, et la CI le constate après coup. C'est déjà bien assez grave pour justifier le correctif, mais il fallait le dire juste.
Le correctif, votre piste 1 étendue à
--check(votre piste 3) :eqs openapi exportrefuse quand le point d'entrée est plus ancien que les sources, et nomme le fichier fautif :Refuser plutôt qu'avertir, parce qu'un contrat n'est pas un document : c'est ce dont les clients sont générés et ce que l'image embarque. C'est D-042.
Deux bornes, pour qu'il ne crie pas à tort :
distoubuild. Le profilnext-fullstackdéclare une source (./apps/web/src/openapi.ts) que Node lit telle quelle : rien n'y est compilé, donc rien ne peut être en retard, et une source plus récente qu'une autre y est la normale ;distappartient (.../apps/api/dist/…→.../apps/api). Remonter à tout le dépôt ferait refuser l'export de l'API parce qu'on vient de toucher au front.Trois tests couvrent ces trois cas, dont celui de
--check.Ce que la barrière ne voit pas, et je préfère l'écrire que vous laisser le découvrir : une mise à jour de
@eqs/*ou d'une autre dépendance qui change la forme du contrat sans qu'aucune source du dépôt ne bouge. Les dates ne peuvent rien y faire. La CI, elle, construit sur un checkout neuf et ne dépend d'aucune date — c'est ce qui la garde utile même une fois cette barrière posée.Ce que je n'ai pas fait, votre piste 2 : enchaîner
build && eqs openapi exportdans le script du gabarit. Ça masquerait la barrière au lieu de l'exercer, et unbuildcomplet à chaque export coûte plus que le message d'erreur. Si l'enchaînement vous manque à l'usage, dites-le.Votre consigne portée à la main peut disparaître à la prochaine version de
@eqs/*:pnpm openapivous dira lui-même quand construire. Branchefix/tickets-socle-du-premier-satellite.Vérifié en 0.8.0 : corrigé, contournement retiré
Repris chez nous le 2026-09-10, sur
@eqs/cli0.8.0. La correction fonctionne, et le contournement est parti.La mesure
distconstruit, puis une route ajoutée aux sources sans reconstruire :Le message nomme le fichier changé et dit quoi faire — c'est mieux que ce que je demandais.
Et le code de sortie est 1, vérifié séparément :
Je le signale parce que c'est le point qui décidait de tout. Le trou du ticket n'était pas l'absence de message : c'était que
--checkse comparait au mêmedistpérimé et laissait passer. Un refus affiché sur un code 0 aurait été un faux correctif — la personne aurait vu l'avertissement, la CI non. Là, la porte se ferme réellement.Après retrait de la route d'essai et reconstruction : « Le contrat committé est à jour », aucun écart sur
openapi.json.Ce que ça change chez nous
Le script racine redevient celui du gabarit :
Notre
pnpm --filter api build &&a disparu, et la décision locale qui le portait devient une ligne d'histoire datée plutôt qu'une décision active. Une divergence de moins avec le gabarit.Un point qui reste, et qui vous appartient
eqs-satellite-template#16décrit la même cause sous une autre forme :pnpm devfaitnode --watch dist/main.jssans que rien ne recompile, donc le serveur sert undistpérimé en silence. Ça nous a encore coûté du temps hier : une vérification de bout en bout échouait sur un défaut corrigé le matin même.Vous aviez noté que la vraie réponse serait, partout où le socle lit
dist/, de comparer sa date à celle des sources et de le dire. C'est exactement ce que faitopenapi exportmaintenant — et ça marche très bien. Le même geste appliqué au scriptdevfermerait l'autre moitié.De notre côté, ce ticket est refermé.
eqs openapi exportrefuse d'exporter parce qu'un fichier de test a bougé — ortest/n'est jamais compilé dansdist/#52