0061 — Accélérer la CI : cache de contenu, parallélisation des jobs, court-circuit élargi
Contexte
Section intitulée « Contexte »La CI principale (ci.yml) tournait en
~9 min médian sur une PR avec code. La mesure des runs réels (gh run view) a
montré trois sources de lenteur, par ordre d’impact :
-
Le cache Turbo ne se réutilisait quasiment jamais. La clé de cache
actions/cacheportait${{ github.sha }}— unique par commit. La clé exacte n’était donc jamais retrouvée d’un run à l’autre ; seul lerestore-keyspartiel (préfixe) rattrapait un cache. Pire, les six jobs écrivaient tous le même chemin.turboavec une clé SHA différente : commeactions/cachene réécrit pas une entrée déjà présente, le cache se figeait sur le premier job qui le peuplait. Conséquence concrète :pnpm lint(ESLint type-aware viaprojectServicesur ~42 paquets) coûtait ~319 s, sans bénéficier du cache des paquets non modifiés. -
Les jobs étaient inutilement sérialisés.
Buildattendaitlint + typecheck + test, etDocumentationattendaitBuild. Or Turbo gère déjà l’ordre réel des tâches (^build) à l’intérieur de chaque job ; chaîner les jobs CI entre eux n’ajoutait que des barrières d’attente. Lint, typecheck et test sont des gates de qualité, pas des dépendances de build. -
Le préambule était dupliqué six fois (checkout, pnpm, Node,
.envSvelteKit, install, cache) et certains jobs ne profitaient d’aucune factorisation. Le job DataOps (Python) ne cachait pas non plus l’environnementuv(re-téléchargement + re-résolution à chaque run).
Le court-circuit « doc-only » (ADR 0034)
existait déjà pour Test/Typecheck/Build, mais Lint et Audit tournaient
toujours : une PR purement documentaire payait quand même ~5 min de lint.
Pourquoi pas de remote cache Turbo
Section intitulée « Pourquoi pas de remote cache Turbo »Un remote cache Turbo (Vercel ou self-hosté) aurait donné les meilleurs hits,
mais : Vercel envoie les hashs/artefacts chez un tiers, et un self-hosté ajoute
une infrastructure à exploiter et sécuriser. Décision : on ne dépend d’aucun
remote cache pour l’instant ; on corrige d’abord le bug de clé, qui capte
l’essentiel du gain à coût nul. TURBO_TOKEN/TURBO_TEAM restent câblés dans
l’env du workflow pour qu’un remote cache puisse être activé plus tard sans
retoucher les jobs.
Décision
Section intitulée « Décision »On accélère la CI sans changer les contrats de la branch protection. Aucun nom de job (= contexte de check requis, ADR 0016) n’est renommé, et aucun job requis ne devient
skipped: le court-circuit reste au niveau step (ADR 0034).
Quatre changements :
-
Clé de cache Turbo par contenu. La clé devient
turbo-v3-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'turbo.json', 'tsconfig.json') }}. Tant que ces fichiers ne changent pas, la clé est stable : le cache se partage réellement entre jobs et entre runs.restore-keysdégrade vers le dernier cache du même OS si la clé exacte manque. -
Action composite locale
.github/actions/setup-workspace. Elle factorise pnpm + Node (cache pnpm) +.envSvelteKit +install --frozen-lockfile --prefer-offline+ cache Turbo. Les jobs l’appellent après leurcheckout. On garde une action composite (partage de steps dans un job) plutôt qu’unworkflow_call(réorchestration de jobs) précisément pour ne pas changer le découpage des jobs ni les noms des checks requis. -
Parallélisation. Tous les jobs lourds dépendent de
[changes]seul.BuildetDocumentationne dépendent plus de lint/typecheck/test. Turbo construit l’ordre réel des tâches en interne. -
Court-circuit doc-only élargi.
LintetAuditadoptent le drapeauRUN(needs.changes.outputs.code == 'true') déjà utilisé par Test/Typecheck/Build. Sur une PR documentaire, leurs steps lourds sont sautés et le job sortsuccess— jamaisskipped. Le job DataOps activeenable-cache: truesursetup-uv.
Accepted (2026-06-13). Étend ADR 0034 (le court-circuit step-level couvre désormais Lint et Audit) et compose avec ADR 0016 (noms de jobs et rapport des contextes requis inchangés).
Conséquences
Section intitulée « Conséquences »Bénéfices. Le cache Turbo profite enfin aux paquets non modifiés : sur une PR qui ne touche que quelques paquets, le lint et le build tombent en grande partie en cache. Le chemin critique raccourcit (Build et Documentation ne sont plus derrière les gates). Une PR documentaire ne paie plus ni lint ni audit. Le préambule dupliqué disparaît (la refonte retire plus de lignes qu’elle n’en ajoute), ce qui réduit aussi le risque de dérive entre jobs.
Prix à payer.
strict: truesur la branch protection (require branches up to date) oblige toujours à reciler surmainavant merge ; l’accélération réduit le coût de ces re-runs mais ne supprime pas la contrainte.- La clé de cache liste explicitement les fichiers de configuration de tâches
(
turbo.json,tsconfig.jsonracine). Si une nouvelle source de config influence les sorties de tâche sans figurer danshashFiles, le cache pourrait servir une entrée périmée — à compléter le cas échéant (faux positif rare, corrigé en bumpantturbo-v3→v4). - Détacher
DocumentationdeBuildsuppose quedocs:buildne lit que des sources (README,package.json, historique git), jamais un artefact buildé du workspace. C’est le cas aujourd’hui ; si la doc venait à consommer undist/, il faudrait rétablir la dépendance.
Garde-fous.
- Ne jamais renommer un job de
ci.ymlsans mettre à jour la liste des contextes requis de la branch protection — un contexte requis introuvable bloque tout merge (corollaire de ADR 0016). - Court-circuit au niveau step, jamais job : invariant repris de ADR 0034.
- Bumper le préfixe de clé (
turbo-vN) à chaque fois qu’on soupçonne un cache empoisonné ou qu’on change la sémantique des inputs Turbo. - Les workflows sont du code à valider.
actionlintgarde désormais.github/workflows/**et.github/actions/**(step du jobAudit) : il attrape unneedsorphelin, unusesnon résolvable ou une expression${{ }}cassée avant qu’un workflow ne parte muet en production — précisément le genre d’erreur qui, sur un check requis, bloque tout merge (corollaire de ADR 0016).