Infra: CI de garde, healthcheck réel, runtime épinglé - #16
Merged
Merged
Conversation
Un hors-série est l'inverse d'une famille sur tous les points : pas de palier, pas de seuil, pas de valeur faciale à gravir ; on le détient ou non ; et un profil qui ne le détient pas n'affiche RIEN, parce qu'un timbre jamais mis en vente ne peut pas manquer à un album. C'est cette asymétrie qui lui vaut son propre module plutôt qu'une cinquième famille : en émettre un ne peut jamais élargir la série courante, donc la mise en page de la feuille reste bornée quoi qu'on ajoute. Sissi est gravée couchée sur un plateau, ce qu'elle fait réellement. Le plateau est en cases carrées — celui du jeu l'est. Elle est unie, gris et blanc : ni rayures sur le dos ni anneaux à la queue, une bicolore n'est pas une tigrée. Rien ne l'attribue pour l'instant. L'easter egg qui la donne n'est pas décidé, donc le timbre est émis mais indécrochable et chaque profil porte une liste vide. Quand le déclencheur existera il n'aura qu'à la remplir. Au passage, le chrome du timbre (dentelure, papier, burelage, cadre, bandeau de pied, oblitération) sort en composant partagé : une valeur de série et un hors-série sont le même objet avec une gravure différente au milieu, et il n'y a toujours qu'une seule source du dessin.
Ni notFoundComponent ni errorComponent sur la racine : une URL inconnue ou un loader en échec tombait sur l'écran par défaut du routeur — hors direction artistique, non localisé, sans issue vers le jeu. Un seul FallbackScreen rend les deux frontières ; l'erreur n'est jamais affichée au visiteur, elle reste dans les logs serveur.
Le serveur ne garde qu'une ligne par (niveau, joueur), pas un historique : un run sans retouche qui ne battait pas le record ne laissait aucune trace, et son sceau mourait avec le localStorage du joueur. Une colonne level_score.ever_clean, écrite par une seconde instruction — l'upsert est gardé par beatenBy, donc il ne peut pas la porter. Le sceau des tableaux ne bouge pas : il décrit la ligne affichée, pas la progression.
Trente-deux symboles sans consommateur hors de leur propre fichier : vingt-six perdent leur export, trois sont supprimés (signOut, SHADE_HEX, FAMILIES), trois relations Drizzle restent intactes — le framework les consomme par convention, pas par import. Aucun changement de comportement.
src/solver est exclu du tsconfig de l'app, donc ni build ni CI ne le couvraient : son propre projet échouait depuis un moment sur six TS2345 dans hunt.test.ts (mods littéral readonly contre MechanicId[] mutable). HuntOpts.mods et randomLevel prennent un readonly — le hunt ne fait que lire cette liste — et le niveau généré copie l'array au lieu de l'aliaser.
L'app avait des comptes et aucun moyen d'en sortir : sur un poste partagé, c'était la session d'un autre qui jouait. Le tri des exports morts l'a révélé — signOut était exporté sans appelant parce que l'écran n'existait pas. Un bouton sous le colophon de /profile/me, gouverné par une prop optionnelle que le profil public ne passe pas. La navigation part de onSuccess : le client better-auth ne rejette pas, un aller-retour manqué laisserait le joueur croire qu'il est déconnecté.
La directive Sitemap: exige une URL absolue, que seul le serveur connaît (BETTER_AUTH_URL) : les garder statiques imposait de coder l'origine de production en dur. public/robots.txt disparaît, une route et un fichier de même URL se disputeraient l'adresse. Le sitemap ne liste que les routes statiques, dérivées de LEVELS. Les profils en sont absents : publiables un par un, pas énumérables dans un fichier servi à tout le monde. Sans origine il répond 404 plutôt que d'enseigner des URL relatives à un crawler ; robots, lui, reste utile et perd juste sa ligne. README et AGENTS.md remis en phase avec le dépôt.
Rien ne tournait automatiquement : le seul garde-fou était le `tsc --noEmit` niché dans le build Railway, donc une suite rouge atteignait la production et n'était visible qu'après le déploiement. `--max-warnings=12` rend exécutable l'invariant que AGENTS.md n'énonçait qu'en prose : les douze avertissements react(only-export-components) sont inhérents aux modules de route TanStack, un treizième est une régression. `packageManager` épingle bun pour Railpack et pour setup-bun d'un même littéral ; bun.lock avait dérivé de package.json, ce que --frozen-lockfile refusait — régénéré ici.
`healthcheckPath: "/"` servait la coquille SPA et répondait 200 avec Postgres à terre : une instance web incapable de joindre sa base était déclarée saine. `probe()` court contre un minuteur de 2 s parce que le Pool n'a aucun délai configuré — sans lui, une base injoignable fait traîner la requête au lieu de la faire échouer. La course vit dans probe() et non sur le Pool partagé : imposer un délai à toutes les requêtes du serveur est une autre décision. Le client est un paramètre plutôt qu'un import de db/index.ts, qui lève à l'import sans DATABASE_URL — la CI n'a pas de base, et une sonde qu'on ne peut pas pointer vers un client en panne ne peut pas être testée en panne. Le corps reste opaque en échec : la route est publique et un message d'erreur du driver nomme l'hôte et l'utilisateur. La raison part dans les logs.
docker-compose fixait postgres:17-alpine quand la production tourne postgres-ssl:18 — un major d'écart entre développement et production. Destructif pour le volume local : Postgres refuse de démarrer sur un répertoire de données d'un major précédent. `docker compose down -v` puis `bun run db:migrate`. AGENTS.md consigne la CI, le healthcheck et ce major.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contexte
Trois manques, vérifiés avant d'écrire une ligne :
.github/n'existait pas. Le seul garde-fou était letsc --noEmitniché dans le build Railway — une suite de tests rougeatteignait la production et n'apparaissait qu'après le déploiement.
healthcheckPath: "/"sert la coquilleSPA et répond 200 même Postgres à terre.
Cette PR porte aussi les 8 commits dont
mainlocal était en avance surorigin: ils passent la CI en entrant, plutôt qu'après coup.Ce qui change
.github/workflows/ci.yml—lint→test→verify→buildsur pushvers
mainet sur PR, un job Ubuntu, concurrence annulant les runs dépassés.Pas de service Postgres : aucun test n'importe
src/db/index.tsni ne litDATABASE_URL, la suite reste sans état.lintpasse àoxlint --max-warnings=12— l'invariant d'AGENTS.mddevientexécutable.
packageManager: "bun@1.4.0"— Railpack le lit (vérifié dans la source :core/providers/node/package_manager.go), etsetup-bunrépète le littéral.bun.lockrégénéré : il avait dérivé depackage.json(entrées mortes@prisma/client,better-sqlite3…). La dérive précède cette branche —vérifié en rejouant
--frozen-lockfilesur un worktree demain./api/health(src/routes/api/health.ts→src/server/health.ts) exécuteselect 1et répond 200{"status":"ok"}ou 503{"status":"degraded"},cache-control: no-store.healthcheckPathpointe dessus.docker-compose.ymlpasse enpostgres:18-alpine, le major de production.Comment vérifier
En local, tout vert :
bun run lint && bun run test && bun run verify && bun run build(12 avertissements, 340 tests, 22 niveaux certifiés).
Le seuil mord : un nouveau module de route porte le compte à 13 et
lintsorten 1.
Le healthcheck, serveur bâti, base injoignable :
Risque / rollback
healthcheckPathne s'applique qu'au prochain déploiement deweb;railway config applyn'a pas été lancé et attend une décision.preDeployCommand: bun run db:migrateexige déjà que la base réponde.docker compose down -vpuisbun run db:migrate.Non couvert
Le cas 200 de
/api/healthn'a pas pu être exercé de bout en bout en local(démon Docker indisponible). Le chemin est couvert par un test unitaire ; le cas
503 et le délai de 2 s le sont sur le serveur bâti.