[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CSSZV2Y7151F3TBR9N8M33":3,"skill-same:01M6CSSZV2Y7151F3TBR9N8M33":126},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":91,"commitSha":92,"skillDirectory":93,"forksCount":14,"repositoryHtmlUrl":94,"repositoryHomepageUrl":6,"ownerHtmlUrl":95,"ownerBlogUrl":6,"ownerDescription":96,"updatedAt":97,"install":98,"resources":111},"01M6CSSZV2Y7151F3TBR9N8M33","volontariapp-ci-tools",null,"Modifier la CI partagée (workflows réutilisables GitHub Actions appelés par tous les repos) ou l'infrastructure locale docker-compose de ci-tools (bases, Redis, MinIO, Datadog). À utiliser avant de toucher ci-tools : tout changement a un impact multi-repo immédiat.","Volontariapp\u002Fmeta","Volontariapp","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F263673138?v=4","01M6CSQNQ3J4SKRWA512JQF5K7","VolontariApp","01M6CSQNV8T2M4PR5A22DKHY5T",0,"MIT","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-ci-tools\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"ci","Tools","volontariapp ci tools","minio","impact","local","redis","code","multi-repo","workflows","e2e","github actions","init","docker","actions","repo","github-actions","docker-compose","compose","infra","2026-11-10 10:12:30",false,"---\nname: volontariapp-ci-tools\ndescription: \"Modifier la CI partagée (workflows réutilisables GitHub Actions appelés par tous les repos) ou l'infrastructure locale docker-compose de ci-tools (bases, Redis, MinIO, Datadog). À utiliser avant de toucher ci-tools : tout changement a un impact multi-repo immédiat.\"\ntype: Catalogue\ntitle: CI partagée et infra locale (ci-tools)\ntags: [ci, github-actions, docker-compose]\nstatus: stable\npaths:\n  - \"ci-tools\u002F.github\u002Fworkflows\u002F**\"\n  - ci-tools\u002Fdocker-compose.yml\n  - \"ci-tools\u002Fscripts\u002F**\"\n  - \"ci-tools\u002Ftemplates\u002F**\"\nmesh_keys:\n  - ci-tools\n  - docker-compose\n  - e2e-orchestrator\n  - service-ci.yml\ngenerated:\n  by: claude-code\u002Fclaude-opus-5-5\n  at: \"2026-10-06T09:12:34Z\"\nverified:\n  - by: \"process:claude-code\"\n    at: \"2026-11-03T14:03:07Z\"\n    digest: d17365b608116a1c\n  - by: claude-code\u002Fagent\n    at: \"2026-11-05T17:34:09Z\"\n    digest: da39a3ee5e6b4b0d\n  - by: claude-code\u002Fagent\n    at: \"2026-11-07T15:34:34Z\"\n    digest: 2757689e145423c0\n  - by: claude-code\u002Fagent\n    at: \"2026-11-07T15:49:21Z\"\n    digest: 2757689e145423c0\n  - by: claude-code\u002Fagent\n    at: \"2026-11-07T15:55:38Z\"\n    digest: 2757689e145423c0\nsources:\n  - id: workflows\n    resource: ci-tools\u002F.github\u002Fworkflows\n    title: Workflows réutilisables\n  - id: compose\n    resource: ci-tools\u002Fdocker-compose.yml\n    title: docker-compose local\n  - id: buckets\n    resource: ci-tools\u002Fscripts\u002Finit-buckets.sh\n    title: init-buckets.sh (MinIO)\n---\n\n# CI partagée et infra locale (`ci-tools`)\n\n`ci-tools` ne contient aucune logique métier : des workflows réutilisables appelés par les autres repos (`uses: Volontariapp\u002Fci-tools\u002F.github\u002Fworkflows\u002F\u003Cx>.yml@main`) et l'environnement local complet. Il est aussi présent comme sous-module `ci-tools\u002F` dans chaque repo : on le modifie dans le repo `meta\u002Fci-tools`, jamais dans une copie de sous-module.\n\n## Workflows réutilisables[^workflows]\n\n| Famille | Workflows |\n| :--- | :--- |\n| Microservices | `service-ci.yml` (lint, tests, couverture), `service-docker-smoke.yml` (healthcheck en conteneur isolé), `service-docker-build-push.yml` (build et push GHCR) |\n| E2E multi-repo | `e2e-orchestrator.yml` (démarre DB et tous les services, lance la suite E2E), `e2e-matrix-checker.yml` (bloque une PR qui viserait des images de test sur `main`) |\n| `npm-packages` | `npm-packages-pipeline.yml` (orchestration, test-build, snapshot, release), `npm-packages-snap-release.yml`, `npm-packages-release.yml`, `npm-packages-emergency-release.yml`, `npm-packages-changelog.yml` |\n| `proto-registry` | `proto-sync.yml` (lint, puis génération et PR dans `npm-packages` sur `main`), `proto-reset.yml` |\n| Sécurité | `security-scan.yml` (Trivy fs : vulnérabilités, secrets, misconfigurations ; input `blocking`, `false` par défaut = mode warning ; appelé en job `security` par `service-ci`, `runner-pipeline`, `npm-packages-pipeline` et `nativapp-ci`, donc aucun repo appelant à modifier ; ci-tools le lance sur lui-même via `security.yml`, et `proto-registry`, `deploy` et `docs` ont leur propre `security.yml` car ils n'utilisent pas la CI partagée) |\n| Notification | `pr-ready-notify.yml` : un message Discord par PR prête (voir ci-dessous) |\n| Autres | `runner-pipeline.yml`, `nativapp-ci.yml`, `validate-compose.yml`, `ghcr-cleanup.yml`, `sync-all.yml` |\n\n`pr-ready-notify.yml` s'appelle comme DERNIER job du `ci.yml` de chaque repo, avec `needs:` listant tous les jobs de la CI du repo (c'est `needs:` qui garantit « CI verte », pas une interrogation de l'API). Le `if:` de l'appelant est `${{ !failure() && !cancelled() && github.event_name == 'pull_request' }}` : `needs:` impose `success()` implicite, donc un job `skipped` (e2e conditionnel) bloquerait la notification. L'appelant déclare aussi `permissions: { contents: read, issues: write, pull-requests: write }` sur ce job : un workflow réutilisable ne peut pas élever les permissions de son appelant, un `permissions: contents: read` au niveau workflow empêche sinon de poser le label. Input `channel` : `backend` (secret `DISCORD_WEBHOOK_BACKEND`) ou `frontend` (`DISCORD_WEBHOOK_FRONTEND`, nativapp). Il notifie si l'auteur est `dependabot[bot]` ou si la PR porte le label `notify-discord`, sans conflit (`mergeable`, relu jusqu'à 10 x 6 s tant que `UNKNOWN`), hors brouillon. Une PR = un message : le label `discord-notified` est posé AVANT l'envoi (le claim) ; s'il ne peut pas l'être le job échoue sans rien envoyer (sinon chaque push renotifierait), et si l'envoi échoue le label est retiré pour qu'un rerun puisse renvoyer. Retirer le label à la main pour être renotifié. Une `concurrency` par PR sérialise deux runs rapprochés. L'appel Discord a `--max-time 20 --retry 3 --retry-all-errors` et le job `timeout-minutes: 3` ; le payload a `allowed_mentions: {parse: []}` et un titre tronqué. Ce job ne doit pas être un check requis (une panne Discord le met en rouge). Limites assumées : aucune autre gâchette n'existe, donc une PR encore `UNKNOWN`\u002Fconflictuelle à la fin de la CI, ou un label `notify-discord` ajouté après la CI, ne notifie qu'au push suivant. Les secrets doivent exister en secrets d'organisation ET en secrets Dependabot ; le `GITHUB_TOKEN` de Dependabot est en lecture seule, le label est donc posé avec `ORG_REPO_TOKEN`. Sans secret webhook, le job avertit et ne fait rien. Les repos qui listent leurs secrets un par un (`npm-packages`, runners) doivent passer les deux webhooks et `ORG_REPO_TOKEN` explicitement. Le titre et les branches de la PR sont lus via `gh`, jamais interpolés avec `${{ }}` dans un script.\n\nDependabot : `ci-tools\u002F.github\u002Fdependabot.yml` ne couvre que ci-tools. Dependabot n'a ni config partagée ni include distant : le fichier doit exister dans chaque repo. Les modèles sont dans `ci-tools\u002Ftemplates\u002Fdependabot\u002F` (`service.yml`, `runner.yml`) et `scripts\u002Frender-dependabot.py` les rend par type de repo (`service`, `service-nodocker`, `monorepo`, `runner`, `actions`). Le workflow `sync-dependabot.yml` (push sur les modèles ou `workflow_dispatch` avec l'input `repos`) ouvre une PR `ci\u002Fdependabot-sync` par repo dont le fichier diffère (secret `ORG_REPO_TOKEN` requis) : ne jamais éditer `\u003Crepo>\u002F.github\u002Fdependabot.yml` à la main, modifier les modèles. `sync-all.yml` ne propage que le pointeur du sous-module, jamais ce fichier. Le modèle runner liste tous les sous-packages dans `directories` pour obtenir une seule PR groupée ; garder cette liste alignée sur l'input `services` du `ci.yml` du runner. Les PR Dependabot portent les labels `docker-e2e` (demandé par le Lead Dev) et `docker-and-e2e` (celui qu'exige `service-docker-build-push.yml`, sans lui la CI complète refuse la PR) ; les deux labels doivent exister dans le repo. Le contrôle du label `docker-and-e2e` (`service-docker-build-push.yml`, `e2e-orchestrator.yml`) relit les labels en direct avec `ORG_REPO_TOKEN` et réessaie jusqu'à 5 minutes (10 x 30 s), car le payload de l'événement est figé et Dependabot peut ajouter ses labels plusieurs minutes après l'ouverture de la PR (sinon `ci-docker` échoue par course, vu sur ms-event#70). Les repos sont publics : un checkout de PR ne doit pas recevoir `token: ${{ secrets.ORG_REPO_TOKEN }}` (une valeur Dependabot invalide casse alors le checkout, `Bad credentials`, même sur un repo public) ; ce secret reste réservé aux étapes qui poussent ou ouvrent des PR dans un autre repo. Dependabot n'expose pas les secrets du repo à la CI : `ORG_REPO_TOKEN` doit aussi être déclaré dans les secrets Dependabot (repo ou organisation) et son `GITHUB_TOKEN` est en lecture seule, ce qui bloque le push GHCR. Peu de PR : npm passe en hebdomadaire (lundi) avec un groupe `dependencies` (mineurs et patchs, `@volontariapp\u002F*` inclus, mergeable seul), un groupe `majors` séparé (un majeur cassé comme typeorm 1.x, bullmq 6 ou expo 58 ne bloque plus le groupe précédent) et un groupe `npm-security` pour que les correctifs de sécurité ne soient pas retardés ; la limite est de 3 PR npm ; `github-actions` et `docker` passent en mensuel. Pour un runner, `directories` réunit tous les sous-packages dans cette même PR, donc les E2E ne tournent qu'une fois. Les majeurs de `@nestjs\u002F*`, `@types\u002Fnode` et de l'image Docker `node`, ainsi que les majeurs et mineurs de `eslint`, `@eslint\u002Fjs` et `typescript` (`typescript-eslint` de `@volontariapp\u002Feslint-config` exige `typescript \u003C5.8`), ainsi que `@opentelemetry\u002Fapi` (`@opentelemetry\u002Fcore` exige `\u003C1.10.0`), sont ignorés (`ignore`) : les `@volontariapp\u002F*` demandent NestJS `^11` en peer (`Validate Peer Requirements` échoue sinon), la CI et `engines` sont en Node 24, et Node 26 n'embarque plus corepack (`corepack: not found` dans le Dockerfile). Pour npm-packages (`monorepo`, `directories` `\u002F` et `\u002Fpackages\u002F*`), `@opentelemetry\u002Fsdk-node`, `@opentelemetry\u002Fexporter-*`, `@opentelemetry\u002Fauto-instrumentations-node`, `bullmq` et `sharp` sont gelés en mineur et majeur : leurs versions 0.x ou mineures ont changé l'API wrappée par les paquets publiés (`metricReader` devenu `metricReaders` dans `monitoring\u002Fsrc\u002Ftracing.ts`, appel à 4 arguments dans `workers\u002Fsrc\u002Fcore\u002Fbase.worker.ts`, `failOnError` retiré de `SharpOptions` dans `storage-pipeline`) ; les migrer dans une PR dédiée avec changesets (règle du STOP). Attention : le motif de remplacement `directory: \"\u002F\"` + `open-pull-requests-limit` de `render-dependabot.py` doit suivre la limite du modèle, sinon `packages\u002F*` disparaît silencieusement. Pour nativapp, `react`, `react-dom`, `react-test-renderer`, `@types\u002Freact`, `typescript-eslint` et `@typescript-eslint\u002F*` sont aussi ignorés pour toute mise à jour (react 19.1.10 casse l'accord avec le SDK qui épingle 19.1.0 ; typescript-eslint résout en 8.72 et lève 10 erreurs `no-unsafe-enum-assignment`), de même que `react-native`, `react-native-*`, `@react-native-community\u002F*` et `@react-navigation\u002Fnative-stack` sont ignorés même en patch : l'étape `expo install --check` du CI refuse toute version différente de celle du SDK (datetimepicker 8.4.7, keyboard-controller 1.18.6, worklets 0.5.2) et react-native 0.81.6 casse 24 tests sous SDK 54 (qui épingle 0.81.5), et `@react-navigation\u002Fbottom-tabs` 7.21 (`render is not a function`) et `typescript-eslint` 8.72 (10 erreurs `no-unsafe-enum-assignment`) sont gelés en mineur ; trouvé par bisect de la PR groupée contre les tests. Pour nativapp (`service-nodocker`), `react`, `react-dom`, `react-native`, `react-native-*`, `expo`, `expo-*`, `@expo\u002F*`, `@react-native\u002F*` et `@types\u002Freact` sont limités aux patchs : leurs versions sont épinglées par l'Expo SDK (la PR groupée qui montait react-native 0.81 vers 0.88 cassait le lint, 35 erreurs contre 0 sur main) et ne bougent que par une montée de SDK (`expo install --fix`). Les majeurs de `typeorm`, `bullmq`, `redis`, `ioredis` et `@opentelemetry\u002Fcore` sont aussi ignorés (typeorm 1.x contredit le `~0.3` de `@volontariapp\u002Fbridge`, `@opentelemetry\u002Fcore` 3 exige `@opentelemetry\u002Fapi` >=1.10) : la PR `majors` ne garde alors que les outils de dev (globals, lint-staged, `@types\u002Fsupertest`). Pour nativapp, les majeurs de `jest`, `jest-expo`, `@types\u002Fjest`, `@testing-library\u002Freact-native` et `@babel\u002Fcore` suivent la version d'Expo SDK. Retirer ces règles seulement après migration et publication de `npm-packages`, et passer image, CI et `engines` ensemble.\n\nRègle : `@main` est consommé immédiatement par tous les repos. Avant de changer les `inputs` ou `secrets` d'un workflow, lister ses appelants (`rg \"ci-tools\u002F.github\u002Fworkflows\u002F\u003Cx>.yml\" \u003Crepo>` repo par repo) et garder la compatibilité.\n\n## Infra locale[^compose]\n\n- `docker-compose.yml` : une base Postgres par service (`postgres-user`, `-post`, `-event`, `-social`, `-ws`, `-storage`, `-ranking` sur le port 5438), `neo4j-social`, `redis` et `redis-ws-service`, MinIO (`minio-init` exécute `scripts\u002Finit-buckets.sh`[^buckets]), un conteneur `*-migration` avant chaque `ms-*`, `api-gateway` et `api-gateway-e2e`. `otel-collector` et `jaeger` sont encore déclarés à côté de `datadog-agent`.\n- Piège : `e2e-orchestrator.yml` lance `--profile migrations pull`, qui tire l'image de TOUS les `*-migration` déclarés. Déclarer `ms-\u003Cx>-migration` avant la première publication de `ghcr.io\u002Fvolontariapp\u002Fms-\u003Cx>` casse l'E2E de tous les repos : ajouter d'abord `postgres-\u003Cx>` seul (suffisant pour `service-ci.yml`), puis le service de migration une fois l'image publiée.\n- Piège (commentaire « CI Snapshot Report » de `npm-packages`) : `npm-packages-snap-release.yml` télécharge les résumés de coverage avec `pattern: coverage-summary-*`. Avec **un seul** artefact correspondant (une PR qui ne construit qu'un paquet), `download-artifact` l'extrait directement dans `coverage-artifacts\u002F` au lieu d'un dossier par artefact, et `scripts\u002Fci-pr-comment.py` (qui lit `coverage-artifacts\u002Fcoverage-summary-\u003Cid>\u002Fcoverage-summary.json`) affichait des tirets. L'étape « Normalize the coverage artifact layout » range le fichier dans son dossier quand un seul paquet est en `BUILD`.\n- Observabilité locale : service `datadog-agent` (`gcr.io\u002Fdatadoghq\u002Fagent:7`), OTLP gRPC sur 4317, APM sur 8126 ; `OTEL_EXPORTER_OTLP_ENDPOINT=http:\u002F\u002Fdatadog-agent:4317` dans `x-ms-common-env` ; `DD_API_KEY` lu dans le `.env` local.\n- `MS_STORAGE_URL: ms-storage:5006` est déjà déclaré, mais `ms-storage` n'écoute pas encore en gRPC (voir `volontariapp-file-storage-flow`).\n\n[^workflows]: Workflows réutilisables\n[^compose]: docker-compose local\n[^buckets]: init-buckets.sh (MinIO)\n","# CI partagée et infra locale (`ci-tools`)\n\n`ci-tools` ne contient aucune logique métier : des workflows réutilisables appelés par les autres repos (`uses: Volontariapp\u002Fci-tools\u002F.github\u002Fworkflows\u002F\u003Cx>.yml@main`) et l'environnement local complet. Il est aussi présent comme sous-module `ci-tools\u002F` dans chaque repo : on le modifie dans le repo `meta\u002Fci-tools`, jamais dans une copie de sous-module.\n\n## Workflows réutilisables[^workflows]\n\n| Famille | Workflows |\n| :--- | :--- |\n| Microservices | `service-ci.yml` (lint, tests, couverture), `service-docker-smoke.yml` (healthcheck en conteneur isolé), `service-docker-build-push.yml` (build et push GHCR) |\n| E2E multi-repo | `e2e-orchestrator.yml` (démarre DB et tous les services, lance la suite E2E), `e2e-matrix-checker.yml` (bloque une PR qui viserait des images de test sur `main`) |\n| `npm-packages` | `npm-packages-pipeline.yml` (orchestration, test-build, snapshot, release), `npm-packages-snap-release.yml`, `npm-packages-release.yml`, `npm-packages-emergency-release.yml`, `npm-packages-changelog.yml` |\n| `proto-registry` | `proto-sync.yml` (lint, puis génération et PR dans `npm-packages` sur `main`), `proto-reset.yml` |\n| Sécurité | `security-scan.yml` (Trivy fs : vulnérabilités, secrets, misconfigurations ; input `blocking`, `false` par défaut = mode warning ; appelé en job `security` par `service-ci`, `runner-pipeline`, `npm-packages-pipeline` et `nativapp-ci`, donc aucun repo appelant à modifier ; ci-tools le lance sur lui-même via `security.yml`, et `proto-registry`, `deploy` et `docs` ont leur propre `security.yml` car ils n'utilisent pas la CI partagée) |\n| Notification | `pr-ready-notify.yml` : un message Discord par PR prête (voir ci-dessous) |\n| Autres | `runner-pipeline.yml`, `nativapp-ci.yml`, `validate-compose.yml`, `ghcr-cleanup.yml`, `sync-all.yml` |\n\n`pr-ready-notify.yml` s'appelle comme DERNIER job du `ci.yml` de chaque repo, avec `needs:` listant tous les jobs de la CI du repo (c'est `needs:` qui garantit « CI verte », pas une interrogation de l'API). Le `if:` de l'appelant est `${{ !failure() && !cancelled() && github.event_name == 'pull_request' }}` : `needs:` impose `success()` implicite, donc un job `skipped` (e2e conditionnel) bloquerait la notification. L'appelant déclare aussi `permissions: { contents: read, issues: write, pull-requests: write }` sur ce job : un workflow réutilisable ne peut pas élever les permissions de son appelant, un `permissions: contents: read` au niveau workflow empêche sinon de poser le label. Input `channel` : `backend` (secret `DISCORD_WEBHOOK_BACKEND`) ou `frontend` (`DISCORD_WEBHOOK_FRONTEND`, nativapp). Il notifie si l'auteur est `dependabot[bot]` ou si la PR porte le label `notify-discord`, sans conflit (`mergeable`, relu jusqu'à 10 x 6 s tant que `UNKNOWN`), hors brouillon. Une PR = un message : le label `discord-notified` est posé AVANT l'envoi (le claim) ; s'il ne peut pas l'être le job échoue sans rien envoyer (sinon chaque push renotifierait), et si l'envoi échoue le label est retiré pour qu'un rerun puisse renvoyer. Retirer le label à la main pour être renotifié. Une `concurrency` par PR sérialise deux runs rapprochés. L'appel Discord a `--max-time 20 --retry 3 --retry-all-errors` et le job `timeout-minutes: 3` ; le payload a `allowed_mentions: {parse: []}` et un titre tronqué. Ce job ne doit pas être un check requis (une panne Discord le met en rouge). Limites assumées : aucune autre gâchette n'existe, donc une PR encore `UNKNOWN`\u002Fconflictuelle à la fin de la CI, ou un label `notify-discord` ajouté après la CI, ne notifie qu'au push suivant. Les secrets doivent exister en secrets d'organisation ET en secrets Dependabot ; le `GITHUB_TOKEN` de Dependabot est en lecture seule, le label est donc posé avec `ORG_REPO_TOKEN`. Sans secret webhook, le job avertit et ne fait rien. Les repos qui listent leurs secrets un par un (`npm-packages`, runners) doivent passer les deux webhooks et `ORG_REPO_TOKEN` explicitement. Le titre et les branches de la PR sont lus via `gh`, jamais interpolés avec `${{ }}` dans un script.\n\nDependabot : `ci-tools\u002F.github\u002Fdependabot.yml` ne couvre que ci-tools. Dependabot n'a ni config partagée ni include distant : le fichier doit exister dans chaque repo. Les modèles sont dans `ci-tools\u002Ftemplates\u002Fdependabot\u002F` (`service.yml`, `runner.yml`) et `scripts\u002Frender-dependabot.py` les rend par type de repo (`service`, `service-nodocker`, `monorepo`, `runner`, `actions`). Le workflow `sync-dependabot.yml` (push sur les modèles ou `workflow_dispatch` avec l'input `repos`) ouvre une PR `ci\u002Fdependabot-sync` par repo dont le fichier diffère (secret `ORG_REPO_TOKEN` requis) : ne jamais éditer `\u003Crepo>\u002F.github\u002Fdependabot.yml` à la main, modifier les modèles. `sync-all.yml` ne propage que le pointeur du sous-module, jamais ce fichier. Le modèle runner liste tous les sous-packages dans `directories` pour obtenir une seule PR groupée ; garder cette liste alignée sur l'input `services` du `ci.yml` du runner. Les PR Dependabot portent les labels `docker-e2e` (demandé par le Lead Dev) et `docker-and-e2e` (celui qu'exige `service-docker-build-push.yml`, sans lui la CI complète refuse la PR) ; les deux labels doivent exister dans le repo. Le contrôle du label `docker-and-e2e` (`service-docker-build-push.yml`, `e2e-orchestrator.yml`) relit les labels en direct avec `ORG_REPO_TOKEN` et réessaie jusqu'à 5 minutes (10 x 30 s), car le payload de l'événement est figé et Dependabot peut ajouter ses labels plusieurs minutes après l'ouverture de la PR (sinon `ci-docker` échoue par course, vu sur ms-event#70). Les repos sont publics : un checkout de PR ne doit pas recevoir `token: ${{ secrets.ORG_REPO_TOKEN }}` (une valeur Dependabot invalide casse alors le checkout, `Bad credentials`, même sur un repo public) ; ce secret reste réservé aux étapes qui poussent ou ouvrent des PR dans un autre repo. Dependabot n'expose pas les secrets du repo à la CI : `ORG_REPO_TOKEN` doit aussi être déclaré dans les secrets Dependabot (repo ou organisation) et son `GITHUB_TOKEN` est en lecture seule, ce qui bloque le push GHCR. Peu de PR : npm passe en hebdomadaire (lundi) avec un groupe `dependencies` (mineurs et patchs, `@volontariapp\u002F*` inclus, mergeable seul), un groupe `majors` séparé (un majeur cassé comme typeorm 1.x, bullmq 6 ou expo 58 ne bloque plus le groupe précédent) et un groupe `npm-security` pour que les correctifs de sécurité ne soient pas retardés ; la limite est de 3 PR npm ; `github-actions` et `docker` passent en mensuel. Pour un runner, `directories` réunit tous les sous-packages dans cette même PR, donc les E2E ne tournent qu'une fois. Les majeurs de `@nestjs\u002F*`, `@types\u002Fnode` et de l'image Docker `node`, ainsi que les majeurs et mineurs de `eslint`, `@eslint\u002Fjs` et `typescript` (`typescript-eslint` de `@volontariapp\u002Feslint-config` exige `typescript \u003C5.8`), ainsi que `@opentelemetry\u002Fapi` (`@opentelemetry\u002Fcore` exige `\u003C1.10.0`), sont ignorés (`ignore`) : les `@volontariapp\u002F*` demandent NestJS `^11` en peer (`Validate Peer Requirements` échoue sinon), la CI et `engines` sont en Node 24, et Node 26 n'embarque plus corepack (`corepack: not found` dans le Dockerfile). Pour npm-packages (`monorepo`, `directories` `\u002F` et `\u002Fpackages\u002F*`), `@opentelemetry\u002Fsdk-node`, `@opentelemetry\u002Fexporter-*`, `@opentelemetry\u002Fauto-instrumentations-node`, `bullmq` et `sharp` sont gelés en mineur et majeur : leurs versions 0.x ou mineures ont changé l'API wrappée par les paquets publiés (`metricReader` devenu `metricReaders` dans `monitoring\u002Fsrc\u002Ftracing.ts`, appel à 4 arguments dans `workers\u002Fsrc\u002Fcore\u002Fbase.worker.ts`, `failOnError` retiré de `SharpOptions` dans `storage-pipeline`) ; les migrer dans une PR dédiée avec changesets (règle du STOP). Attention : le motif de remplacement `directory: \"\u002F\"` + `open-pull-requests-limit` de `render-dependabot.py` doit suivre la limite du modèle, sinon `packages\u002F*` disparaît silencieusement. Pour nativapp, `react`, `react-dom`, `react-test-renderer`, `@types\u002Freact`, `typescript-eslint` et `@typescript-eslint\u002F*` sont aussi ignorés pour toute mise à jour (react 19.1.10 casse l'accord avec le SDK qui épingle 19.1.0 ; typescript-eslint résout en 8.72 et lève 10 erreurs `no-unsafe-enum-assignment`), de même que `react-native`, `react-native-*`, `@react-native-community\u002F*` et `@react-navigation\u002Fnative-stack` sont ignorés même en patch : l'étape `expo install --check` du CI refuse toute version différente de celle du SDK (datetimepicker 8.4.7, keyboard-controller 1.18.6, worklets 0.5.2) et react-native 0.81.6 casse 24 tests sous SDK 54 (qui épingle 0.81.5), et `@react-navigation\u002Fbottom-tabs` 7.21 (`render is not a function`) et `typescript-eslint` 8.72 (10 erreurs `no-unsafe-enum-assignment`) sont gelés en mineur ; trouvé par bisect de la PR groupée contre les tests. Pour nativapp (`service-nodocker`), `react`, `react-dom`, `react-native`, `react-native-*`, `expo`, `expo-*`, `@expo\u002F*`, `@react-native\u002F*` et `@types\u002Freact` sont limités aux patchs : leurs versions sont épinglées par l'Expo SDK (la PR groupée qui montait react-native 0.81 vers 0.88 cassait le lint, 35 erreurs contre 0 sur main) et ne bougent que par une montée de SDK (`expo install --fix`). Les majeurs de `typeorm`, `bullmq`, `redis`, `ioredis` et `@opentelemetry\u002Fcore` sont aussi ignorés (typeorm 1.x contredit le `~0.3` de `@volontariapp\u002Fbridge`, `@opentelemetry\u002Fcore` 3 exige `@opentelemetry\u002Fapi` >=1.10) : la PR `majors` ne garde alors que les outils de dev (globals, lint-staged, `@types\u002Fsupertest`). Pour nativapp, les majeurs de `jest`, `jest-expo`, `@types\u002Fjest`, `@testing-library\u002Freact-native` et `@babel\u002Fcore` suivent la version d'Expo SDK. Retirer ces règles seulement après migration et publication de `npm-packages`, et passer image, CI et `engines` ensemble.\n\nRègle : `@main` est consommé immédiatement par tous les repos. Avant de changer les `inputs` ou `secrets` d'un workflow, lister ses appelants (`rg \"ci-tools\u002F.github\u002Fworkflows\u002F\u003Cx>.yml\" \u003Crepo>` repo par repo) et garder la compatibilité.\n\n## Infra locale[^compose]\n\n- `docker-compose.yml` : une base Postgres par service (`postgres-user`, `-post`, `-event`, `-social`, `-ws`, `-storage`, `-ranking` sur le port 5438), `neo4j-social`, `redis` et `redis-ws-service`, MinIO (`minio-init` exécute `scripts\u002Finit-buckets.sh`[^buckets]), un conteneur `*-migration` avant chaque `ms-*`, `api-gateway` et `api-gateway-e2e`. `otel-collector` et `jaeger` sont encore déclarés à côté de `datadog-agent`.\n- Piège : `e2e-orchestrator.yml` lance `--profile migrations pull`, qui tire l'image de TOUS les `*-migration` déclarés. Déclarer `ms-\u003Cx>-migration` avant la première publication de `ghcr.io\u002Fvolontariapp\u002Fms-\u003Cx>` casse l'E2E de tous les repos : ajouter d'abord `postgres-\u003Cx>` seul (suffisant pour `service-ci.yml`), puis le service de migration une fois l'image publiée.\n- Piège (commentaire « CI Snapshot Report » de `npm-packages`) : `npm-packages-snap-release.yml` télécharge les résumés de coverage avec `pattern: coverage-summary-*`. Avec **un seul** artefact correspondant (une PR qui ne construit qu'un paquet), `download-artifact` l'extrait directement dans `coverage-artifacts\u002F` au lieu d'un dossier par artefact, et `scripts\u002Fci-pr-comment.py` (qui lit `coverage-artifacts\u002Fcoverage-summary-\u003Cid>\u002Fcoverage-summary.json`) affichait des tirets. L'étape « Normalize the coverage artifact layout » range le fichier dans son dossier quand un seul paquet est en `BUILD`.\n- Observabilité locale : service `datadog-agent` (`gcr.io\u002Fdatadoghq\u002Fagent:7`), OTLP gRPC sur 4317, APM sur 8126 ; `OTEL_EXPORTER_OTLP_ENDPOINT=http:\u002F\u002Fdatadog-agent:4317` dans `x-ms-common-env` ; `DD_API_KEY` lu dans le `.env` local.\n- `MS_STORAGE_URL: ms-storage:5006` est déjà déclaré, mais `ms-storage` n'écoute pas encore en gRPC (voir `volontariapp-file-storage-flow`).\n\n[^workflows]: Workflows réutilisables\n[^compose]: docker-compose local\n[^buckets]: init-buckets.sh (MinIO)\n",{"name":5,"tags":49,"type":50,"paths":51,"title":56,"status":57,"sources":58,"verified":68,"generated":84,"mesh_keys":87,"description":7},[24,40,41],"Catalogue",[52,53,54,55],"ci-tools\u002F.github\u002Fworkflows\u002F**","ci-tools\u002Fdocker-compose.yml","ci-tools\u002Fscripts\u002F**","ci-tools\u002Ftemplates\u002F**","CI partagée et infra locale (ci-tools)","stable",[59,62,64],{"id":33,"title":60,"resource":61},"Workflows réutilisables","ci-tools\u002F.github\u002Fworkflows",{"id":42,"title":63,"resource":53},"docker-compose local",{"id":65,"title":66,"resource":67},"buckets","init-buckets.sh (MinIO)","ci-tools\u002Fscripts\u002Finit-buckets.sh",[69,73,77,80,82],{"at":70,"by":71,"digest":72},"2026-11-03T14:03:07Z","process:claude-code","d17365b608116a1c",{"at":74,"by":75,"digest":76},"2026-11-05T17:34:09Z","claude-code\u002Fagent","da39a3ee5e6b4b0d",{"at":78,"by":75,"digest":79},"2026-11-07T15:34:34Z","2757689e145423c0",{"at":81,"by":75,"digest":79},"2026-11-07T15:49:21Z",{"at":83,"by":75,"digest":79},"2026-11-07T15:55:38Z",{"at":85,"by":86},"2026-10-06T09:12:34Z","claude-code\u002Fclaude-opus-5-5",[88,41,89,90],"ci-tools","e2e-orchestrator","service-ci.yml","main","6c8390fd5e09e7a0eee8194ce05bccbd17ffe994",".agents\u002Fskills\u002Fvolontariapp-ci-tools","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta","https:\u002F\u002Fgithub.com\u002FVolontariapp","Volontariapp — Application Web\u002FMobile scalabelle construite sur une architecture microservices événementielle (Event-Driven Architecture), implémentant le Trans","2026-11-10 23:40:22",{"source":8,"skillName":5,"commands":99,"prompt":109,"zipAvailable":110},[100,103,106],{"tool":101,"command":102},"npx","npx skills add Volontariapp\u002Fmeta --skill volontariapp-ci-tools",{"tool":104,"command":105},"bunx","bunx skills add Volontariapp\u002Fmeta --skill volontariapp-ci-tools",{"tool":107,"command":108},"pnpm","pnpm dlx skills add Volontariapp\u002Fmeta --skill volontariapp-ci-tools","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-ci-tools\u002FSKILL.md",true,[112,118,122],{"path":113,"resourceType":114,"resourceTypeDesc":115,"sizeBytes":116,"sourceHtmlUrl":117},"index.md","REFERENCE","参考文档",632,"https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-ci-tools\u002Findex.md",{"path":119,"resourceType":114,"resourceTypeDesc":115,"sizeBytes":120,"sourceHtmlUrl":121},"log.md",6051,"https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-ci-tools\u002Flog.md",{"path":123,"resourceType":114,"resourceTypeDesc":115,"sizeBytes":124,"sourceHtmlUrl":125},"references\u002Flessons.md",17274,"https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-ci-tools\u002Freferences\u002Flessons.md",[127,152,179,204,229,248],{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":44},"01M6CSW3Y5ZHVBF5BX7S5BAVS0","volontariapp-user-account-deletion","Suppression de compte en deux temps (PENDING_DELETION): requestDeletionWithUserDeleted, finalizeDeletion, fan-out de user.deleted vers les domaines et gather ws. A utiliser pour tout changement de domain-user touchant la suppression, le statut ou le cycle de vie d'un user.","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-user-account-deletion\u002FSKILL.md",[],[134,135,136,137,138,139,140,141,142,31,143,144,145,146,147,148,149,150,151],"volontariapp user account deletion","user","account deletion","deletion","cycle","gather","sonnet","pending","de","claude-code","reference","npm","user-account-deletion","src","en","domain","npm-packages","packages",{"skillNo":153,"name":154,"nameZh":6,"description":155,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":163,"publishedAt":44},"01M6CSVC1ZW30DDFZ8VMEYAAK6","volontariapp-proto-contract-evolution","Modifier un contrat Protobuf de proto-registry (champ, message, enum, RPC) sans casser la compatibilité wire, puis suivre la cascade proto-registry, PR automatique dans npm-packages, publication, consommateurs. À utiliser avant toute édition d'un .proto.","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-proto-contract-evolution\u002FSKILL.md",[158],{"categoryNo":159,"categoryName":160,"taxonomyType":161,"taxonomyTypeDesc":162},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[164,165,166,167,31,33,168,151,169,24,170,171,172,145,173,174,175,176,177,178],"evolution","volontariapp proto contract evolution","grpc","lint","analyze","rpc","pr","protobuf","message","registry","sans","sync","yaml","playbook","github",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":183,"categories":184,"keywords":188,"publishedAt":44},"01M6CSV99ET2Z9W4W4DAK4CMQP","volontariapp-meta-scripts","Utiliser et faire evoluer la CLI Node de meta (setup, doctor, migrate, certs, repos, bump) et le manifeste repos.json, source unique des repos et unites du workspace.","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-meta-scripts\u002FSKILL.md",[185],{"categoryNo":186,"categoryName":187,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[189,190,191,31,192,143,193,194,195,196,197,198,199,200,201,202,203],"Meta","volontariapp meta scripts","repos","migrate","setup","meta-scripts","json","node","workspace","shell","bump","cli","doctor","draft","install",{"skillNo":205,"name":206,"nameZh":6,"description":207,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":208,"categories":209,"keywords":213,"publishedAt":44},"01M6CSTPHNTGE0WF5CPE8W7ZYQ","volontariapp-implement-async-event-flow","Guide pas-à-pas pour concevoir et implémenter un flux asynchrone complet (messaging -> outbox -> post-processors -> ws-service -> client).","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvolontariapp-implement-async-event-flow\u002FSKILL.md",[210],{"categoryNo":211,"categoryName":212,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[214,215,216,217,218,219,147,220,221,173,222,223,150,224,225,151,226,227,31,228],"async","implement","volontariapp implement async event flow","flow","event","messaging","mapping","flux","streams","flows","shared","outbox","processor","architecture","runner",{"skillNo":230,"name":231,"nameZh":6,"description":232,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":233,"categories":234,"keywords":238,"publishedAt":44},"01M6CSRQHS0T9955M8CTA5BYJQ","research","Investigate a question against high-trust primary sources and capture the findings as a Markdown file in the repo. Use when the user wants a topic researched, docs or API facts gathered, or reading legwork delegated to a background agent.","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fplugins\u002Fmatt-pocock\u002Fskills\u002Fresearch\u002FSKILL.md",[235],{"categoryNo":236,"categoryName":237,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000021","资料检索与研究",[231,239,135,240,39,241,242,243,244,245,246,247],"api","file","markdown","docs","capture","use","question","trust","investigate",{"skillNo":249,"name":250,"nameZh":6,"description":251,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":252,"categories":253,"keywords":257,"publishedAt":44},"01M6CSRMZ82M1YK96Z4SW1STTD","prototype","Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.","https:\u002F\u002Fgithub.com\u002FVolontariapp\u002Fmeta\u002Fblob\u002Fmain\u002F.agents\u002Fplugins\u002Fmatt-pocock\u002Fskills\u002Fprototype\u002FSKILL.md",[254],{"categoryNo":255,"categoryName":256,"taxonomyType":161,"taxonomyTypeDesc":162},"01JBSEEDCAT000000000000032","设计师",[250,258,135,259,244,260,245,261,262,263,264,265],"check","ui","look","design","explore","model","build","state"]