CI & Actions
Chaque workflow se trouve dans .github/workflows/. Les sections ci-dessous les regroupent par déclencheur : automatiques lors d’événements git, ou workflows déclenchés par les mainteneurs/advisory via workflow_dispatch et planifications.
Flux de travail automatiques
Portail de qualité (ci.yml)
Se déclenche à chaque PR ciblant master et lors des pushes approuvés vers master. Job composite avec plusieurs branches de matrice :
- fmt :
cargo fmt --all -- --check - history-guard : récupère l’historique complet et vérifie le commit testé par rapport à
origin/master; les pull requests utilisent explicitementgithub.event.pull_request.head.sha, tandis que les pushes de confiance et les exécutions de la merge-queue utilisentgithub.sha. Le garde-fou et son test de fixture rejettent un résultat vide degit merge-base, empêchant une seconde racine greffée de faire s’effondrergit blameaprès la fusion - lint :
cargo clippy --workspace --exclude zeroclaw-desktop --all-targets --features ci-all -- -D warnings, puiscargo doc --no-deps --workspace --exclude zeroclaw-desktop(les avertissements de rustdoc sont fatals via.cargo/config.tomlbuild.rustdocflags; desktop est exclu pour correspondre àxtask build_api/ au déploiement de la documentation et éviter GTK/glib-syssur l’exécuteur de lint), ainsi que le contrôle d’hygiène des commentaires - build : matrice inconditionnelle pour
x86_64-unknown-linux-gnuetaarch64-apple-darwin; un job windows-build distinct vérifiex86_64-pc-windows-msvclorsque la propriété des paquets sélectionne l’une de ses commandes - check : deux passes où les avertissements sont fatals sur l’espace de travail (à l’exclusion de
zeroclaw-desktop) : sans fonctionnalités par défaut ; et avec les fonctionnalités par défaut plus--all-targets, qui est la seule étape compilant les cibles de test sur la surface de fonctionnalités par défaut. La surface avec toutes les fonctionnalités est compilée et vérifiée par lint, dont la passe clippy exécute--features ci-all --all-targetssur le même espace de travail ; une étapecargo checkdistincte avec toutes les fonctionnalités dupliquerait cette compilation sans apporter de couverture supplémentaire. - check-32bit :
i686-unknown-linux-gnusans les fonctionnalités par défaut - check-plugin-backends : sélectionné par
scripts/ci/plugin_backend_change_filter.sh. Compilezeroclaw-pluginsavec chaque backend d’exécution, puis exécute les tests des composants, de l’admission à l’exécution et du CLI racine pour les plugins, sousplugins-wasm-cranelift. Un job séparé,plugin artifact smoke, compile le binairezeroclawavec la même fonctionnalité et exécutescripts/ci/plugin_artifact_smoke.shcontre ce binaire, avec les paquets produits parscripts/ci/build_plugin_smoke_packages.sh. Le smoke test accepte n’importe quel binairezeroclaw, utilise un répertoire de configuration jetable et le fournisseur local scriptéscripts/ci/plugin_smoke_provider.py, et pilote les commandes de l’opérateur :plugin install,config setet un tour d’agent qui exécute l’outil. Il vérifie qu’un plugin installé reste inutilisé tant que l’opérateur n’a pas activé le système de plugins, activé la découverte et approuvé l’outil ; que les valeurs configurées pour un paquet qui ne les demande pas ne sont pas transmises ; qu’une charge utile avec une mauvaise empreinte (digest), une charge utile qui n’est pas un composant, et un composant destiné à un autre monde WIT sont refusés ; et que le mode de signature strict refuse un paquet non signé, non approuvé ou modifié, tout en exécutant un paquet approuvé. La dernière étape de la leg exécute le smoke test avec l’attente inverse et exige le code de sortie réservé à un échec de vérification. Le tour d’agent dépend aussi de crates que le filtre de chemins ne sélectionne pas, commezeroclaw-providers,zeroclaw-tools,zeroclaw-memoryetzeroclaw-api. Une régression dans ces crates n’apparaît donc d’abord que lors des exécutions de la file de fusion (merge queue) et demaster, où le job s’exécute toujours. - bench : vérifie la compilation des benchmarks ; s’exécute uniquement sur les poussées vers
master, les exécutions de la file de fusion (merge queue) et les déclenchements manuels. Les PR ignorent cette étape (la porte qualité traite l’ignorance comme non bloquante), si bien que la dégradation des benchmarks reste détectée avant une version, sans consommer la compilation à chaque PR. - test : la passerelle hôte autonome du protocole firmware issue de
scripts/ci/firmware_protocol_gate.shetcargo nextest run --locked --workspace --exclude zeroclaw-desktopsous Linux, y compris les garde-fous d’architecture d’isolation des écritures de configuration et de couverture Fluent (aucune chaîne utilisateur en dur). Une étape finale informative rejoue les scénarios de trames dorées de la passerelle (crates/zeroclaw-gateway/tests/golden_frames.rs: chat WebSocket, webhook JSON et SSE,/api/events, et ACP) par rapport à leurs fixtures enregistrées avec--run-ignored only. Elle réutilise la compilation du job, écrit toute dérive dans le résumé de l’étape avec un avertissement, et ne fait jamais échouer le job ; elle reste informative tant que la passerelle ne communique pas avec le cœur via RPC. - test-tool-features: les tests des adaptateurs SaaS, CLI de codage et externes opt-in, que
defaultde Cargo ne compile plus :cargo nextest runsur les tests de bibliothèque de root, config, tools, runtime, channels et gateway, plus les tests d’intégration des modèles de rapport de root, avec--features tools-compat.lintcompile le même code sousci-allmais n’exécute aucun test - memory-postgres-test :
cargo nextest run --locked -p zeroclaw-memory --features memory-postgres, suivi des tests d’acceptation ignorés et sérialisés du crate, exécutés contre un service PostgreSQL 17 éphémère - parallel-runtime-test : tests répétés d’exécution/canal dans le même processus depuis
scripts/ci/parallel_runtime_test_gate.sh, exécutés en parallèle avec la tâche de test principale pour les chemins de PR pertinents et systématiquement lors des poussées surmasteret des exécutions de la file d’attente de fusion - sécurité :
cargo deny check - nix-eval : évalue les assertions du module NixOS (vérification de flake
nixos-module-eval) - docs-style : lint du Markdown, vérification des tirets longs dans le texte et contrôle des liens sur les lignes modifiées via
scripts/ci/docs_quality_gate.shetscripts/ci/docs_links_gate.sh
Les jobs Linux de la required gate s’exécutent sur Blacksmith, à l’exception du contrôle Landlock qui dépend du noyau : la flotte de compilation intensive sur blacksmith-8vcpu-ubuntu-2404, et l’ensemble des jobs de maintenance (fmt, les détecteurs de changements, les gardes du dépôt, les contrôles de documentation et de politique, les vérifications Nix, le test de fumée du conteneur, security, web-permission-tests et l’agrégateur gate lui-même) sur blacksmith-4vcpu-ubuntu-2404. La seule exception Linux délibérée est test-landlock, qui reste sur ubuntu-latest hébergé par GitHub, car le contrôle du LSM Landlock dépend du noyau hébergé. La récupération des tâches Windows utilise blacksmith-8vcpu-windows-2025 ; macOS et les autres jobs Windows conservent leurs runners hébergés par GitHub. Cette répartition existe parce que l’attribution des runners hébergés par GitHub a été bloquée à l’échelle de l’organisation le 2026-09-14, alors que Blacksmith continuait de répondre : avec les jobs de maintenance déplacés, une panne des runners hébergés ne coûte que les contrôles spécifiques à la plateforme au lieu de toute la required gate. Tous les jobs déclarent directement leurs labels de runner et leurs entrées de fournisseur de cache ; tests/architecture/ci_runner_labels.rs vérifie les inventaires Linux (flotte de compilation, flotte de maintenance et liste d’autorisation des Linux hébergés), ainsi que le contrat du runner Windows réservé à la récupération et de l’écrivain de cache. Les jobs de travail n’attendent pas fmt ; le compromis assumé est un surcoût de runner sur les PR qui échouent au formatage. CI Required Gate dépend toujours directement de fmt et agrège les résultats requis, de sorte que les erreurs de formatage bloquent toujours la fusion. Le workflow Windows consultatif, déclenché par label, reste hors de cette gate ; son sélecteur Linux s’exécute également sur la classe Blacksmith 4vcpu. La protection de la branche épingle le job composite de la gate. L’exécution sur push vers master conserve le même signal de qualité tout en alimentant les caches Rust de confiance pour les exécutions de PR ultérieures.
Les pull requests et les entrées de file de fusion qui modifient [workspace.package] version vers une version stable X.Y.Z exécutent aussi crates-preflight, qui appelle pub-crates.yml avec stage: preflight, sans secret, et avec le label de runner de la flotte de compilation. crates-preflight-changes décide cela avec scripts/ci/crates_preflight_trigger.sh ; toutes les autres modifications ignorent les deux jobs, et un job ignoré ne fait pas échouer la validation. Comme le job est un appel de workflow réutilisable sans runs-on, tests/architecture/ci_runner_labels.rs l’inventorie séparément et exige qu’il passe le label de la flotte de compilation.
Les inventaires des runners couvrent à la fois ci.yml et windows-tests.yml, avec des identités de jobs qualifiées par workflow afin que des jobs portant le même nom ne puissent pas se masquer mutuellement. La matrice build Linux/macOS n’attend aucun détecteur. Les jobs Windows distincts de compilation, de service et de récupération attendent le détecteur windows-required-changes de Blacksmith. Ce détecteur lit la propriété des packages avec cargo metadata --locked --offline --no-deps, sans résolution de registre ni téléchargement. Le détecteur path-changes, non lié à Rust, reste indépendant de ce graphe de packages.
Sur les seuls pushes sur master, les jobs gourmands en compilation (et les détecteurs qui conditionnent les jobs clippy Windows et parallel-runtime) attendent d’abord cinq minutes dans master-debounce. Les pushes sur master arrivent par rafales, et le cancel-in-progress au niveau du run n’annule sinon un push remplacé qu’une fois que sa flotte de compilation a démarré ; avec l’attente, un push remplacé pendant la fenêtre est annulé alors que ce job peu coûteux est encore en veille. Le job est ignoré sur les pull requests et les runs de merge queue, et un job ignoré se résout sans runner, si bien que leur latence reste inchangée. Comme GitHub propage un ancêtre ignoré via un success() implicite, chaque job en aval de master-debounce porte une condition !cancelled() explicite, plus une vérification du résultat pour chaque dépendance, et tests/architecture/ci_runner_labels.rs échoue si ce n’est pas le cas. CI Required Gate dépend de master-debounce, donc un échec du debounce ne peut pas laisser des jobs de compilation ignorés derrière une porte au vert. Le compromis est de cinq minutes supplémentaires avant qu’un run master ne commence à compiler, ce qui retarde aussi d’autant l’amorçage des caches Rust de master.
Une exécution CI requise et fraîche constitue généralement la preuve partagée des surfaces de Cargo qu’elle exécute réellement. Une réexécution locale de la même commande Cargo sur le même HEAD, la même cible et le même jeu de fonctionnalités ne constitue qu’une confiance redondante, pas une preuve plus solide. Avant de demander l’exécution supplémentaire de Cargo ou de Clippy, comparez la surface modifiée avec les fichiers de workflow actuels et les vérifications effectives sur la PR. Une validation supplémentaire est justifiée là où la validation requise ne prouve pas l’objet de la revue :
- une plateforme a reçu des vérifications de compilation mais pas de tests ;
- une plateforme, un crate ou un chemin est en dehors de la tâche de lint requise ;
- un changement de bureau n’a pas déclenché le workflow de bureau ;
- une cible de release est en dehors de la matrice PR et n’est couverte que par les workflows release/manual ;
- un CI périmé, annulé, ignoré ou indisponible n’est pas une preuve fraîche.
Lorsqu’une définition ou un import est conditionné par une fonctionnalité, comparez son prédicat cfg à celui de chaque utilisateur. Validez à la fois la configuration activée et chaque configuration désactivée pertinente : un passage avec la fonctionnalité activée prouve que l’utilisateur fonctionne toujours, tandis que la vérification no-default-features à l’échelle de l’espace de travail détecte les écarts générant des avertissements, comme les définitions privées ou les imports inutilisés. Cette passe exécute cargo check sans --all-targets, et ne compile donc jamais les cibles de test : un utilitaire conditionné par le simple test, dont les seuls appelants sont conditionnés par une fonctionnalité, est plutôt détecté par l’étape default-features/all-targets. Des combinaisons ciblées de fonctionnalités restent nécessaires lorsque aucune des configurations CI requises n’exerce le prédicat modifié.
La tâche PostgreSQL de mémoire exécute tous les tests de zeroclaw-memory non ignorés avec memory-postgres activé, puis exécute les tests ignorés de la crate en série, avec ZEROCLAW_TEST_POSTGRES_URL pointant vers son service jetable. Pour reproduire localement la moitié reposant sur la base de données, fournissez une base PostgreSQL isolée et exécutez ZEROCLAW_TEST_POSTGRES_URL=postgres://<user>:<password>@127.0.0.1:5432/<database> cargo test --locked -p zeroclaw-memory --features memory-postgres -- --ignored --test-threads=1. Chaque test d’acceptation possède un schéma au nom unique et ne supprime que ce schéma.
La compilation Windows requise reste automatique sans label, mais elle ne vérifie que le code que les commandes peuvent compiler. Sur les PR, scripts/ci/windows_test_scope.py --required-jobs réutilise la classification par racine de package et la fermeture des dépendances inverses. La commande racine s’exécute lorsque la fermeture affectée contient zeroclaw ; la commande voice-wake s’exécute lorsqu’elle contient zeroclaw-channels. Les arêtes de chemin internes incluent de manière conservatrice les dépendances optionnelles, spécifiques à une cible, de build et de développement. Une modification de ZeroCode seul ne déclenche pas ces commandes sans rapport. Les manifestes de workspace, les fichiers de verrouillage, les toolchains, la configuration Cargo, les contrôles de workflow/action, les chemins inconnus, ainsi que les preuves manquantes ou malformées sélectionnent tous les jobs Windows requis. Les pushes sur master et les builds de merge queue exécutent toujours tous ces jobs. La compilation Linux/macOS reste inconditionnelle.
Le test de fumée du service compile windows_service_smoke_fixture, qui gère son démon synthétique et la répartition des arguments. Les modifications de la CLI racine dans src/main.rs ne sélectionnent pas automatiquement ce binaire. Le code source du service, le câblage des modules runtime, la fixture et le script de test de fumée, les dépendances de configuration et de lancement, ainsi que les contrôles pertinents des manifestes et de la compilation restent couverts. La tâche de récupération du propriétaire de tâche conserve ses déclencheurs directs pour le plan de contrôle et le câblage des modules runtime. Les manifestes de paquets et les scripts de compilation sélectionnent ces tâches natives lorsque leur fermeture de dépendances inverses atteint le runtime ; les manifestes non liés de la passerelle ou des applications autonomes ne les sélectionnent pas. Le point d’entrée shell de récupération délègue à ce même sélecteur de paquets au lieu de maintenir un second registre de propriété.
Les tests de récupération s’exécutent dans leur propre job windows-task-owner-recovery sur blacksmith-8vcpu-windows-2025, en parallèle de la compilation, car leur harnais de tests unitaires à l’exécution ne peut pas réutiliser la sortie de cargo check. Un job non sélectionné se termine en statut « skipped » avant d’allouer un runner Windows. CI Required Gate consomme directement le détecteur et chaque job Windows requis, rejette les sorties de sélection manquantes et exige que chaque job sélectionné réussisse. Un job sélectionné mais ignoré (skipped), en échec ou annulé ne peut pas être compté comme un saut intentionnel.
La récupération appelle directement l’action épinglée Swatinem/rust-cache avec shared-key: windows-recovery-blacksmith, plutôt que de supposer que le cache du runner de build GitHub possède un hash d’environnement Rust compatible. Seules les exécutions push réussies sur refs/heads/master enregistrent ce cache de dépendances dédié ; les exécutions de PR et de file de fusion (merge queue) se contentent de le restaurer. En cas d’absence dans le cache (miss), le build s’exécute normalement et n’ignore aucun des deux tests. Le nettoyage par défaut des artefacts de l’espace de travail reste activé, donc le harnais d’exécution se compile toujours. Cela ajoute un espace de noms de cache qui consomme le stockage du dépôt ; la compilation et le smoke test de service conservent leurs runners et caches existants.
Tests de fenêtres consultatives conditionnés par label (windows-tests.yml)
Utilisez ci:windows lorsque l’exécution des tests sous Windows peut combler un écart important laissé par la simple compilation. Les cas typiques incluent les modifications du cycle de vie des processus ou des services, l’exécution de PowerShell, la gestion du système de fichiers et des chemins, l’encodage, les dépendances natives, et les modifications de la sélection ou de l’exécution de la CI Windows. Ce n’est pas une étiquette par défaut pour toute PR Rust, pour une modification de la documentation uniquement, ni pour un échec de test Linux sans rapport.
Ajoutez l’étiquette une fois que la proposition est suffisamment stable pour produire des résultats Windows exploitables. Conservez-la pendant les itérations centrées sur Windows, lorsque chaque mise à jour nécessite une nouvelle exécution. Retirez-la après la fin d’une validation ponctuelle, si les envois ultérieurs n’ont pas besoin de cette couverture. La compilation Windows obligatoire reste automatique sans l’étiquette. Les tests de plateforme nocturnes servent de filet de sécurité après la fusion et ne remplacent pas une exécution avant fusion lorsqu’une modification nécessite une vérification propre à Windows.
Ajoutez le label ci:windows à une PR ciblant master pour activer les tests Windows consultatifs. L’ajout de ce label démarre le workflow pour la révision actuelle de la PR. Les push et les événements de réouverture le relancent tant que le label est présent. L’ajout d’autres labels ne relance pas les tests et n’annule pas une exécution Windows en cours. Retirer le label arrête les exécutions automatiques futures, mais n’annule pas une exécution déjà en cours. Le label est un interrupteur de coût de calcul, et non une approbation de sécurité ; les exigences habituelles d’approbation des workflows de fork GitHub s’appliquent toujours.
Le workflow utilise l’événement pull_request ordinaire en lecture seule, et non pull_request_target, et teste le SHA de fusion figé de l’événement. Il n’a pas de déclencheur de dispatch manuel. Le séparer de ci.yml permet à l’ajout de labels de ne démarrer que le workflow consultatif, sans relancer la CI requise. La porte requise ne dépend pas de ces jobs.
I can’t verify a pass from here. I don’t have the selector summary, the Windows job’s conclusion, or its logs, and I can’t fetch them. I also work in this session only as a French translator for documentation strings, so I can’t run or inspect CI.
To check the Windows job, paste or attach these:
- Selector summary: the revision tested and the scope the selector chose.
- Windows job conclusion and step results: each step’s status, including steps with
continue-on-error, since a green workflow can hide a failed step. - Relevant log sections: the test runner output and any setup, build, or selection errors.
With those, I can report the tested revision, the selected scope, and the phase that failed. I’ll separate test failures from selection or workflow failures, and I won’t cite a pass unless the test step’s own log shows one.
Le sélecteur Linux vérifie le formatage, compare le SHA de base de la PR avec la révision de fusion extraite, et génère les métadonnées de l’espace de travail Cargo. Il écrit le mode de référence, les packages au format JSON, la reason concise et needs_plugin_host dans les sorties du job et dans le résumé de l’étape. Le label active le sélecteur existant, et non une suite complète inconditionnelle. Les tests de plateforme nocturnes restent le filet de sécurité automatique après fusion.
skip signifie qu’aucun chemin modifié n’affecte la compilation Rust ni les tests couverts par la suite actuelle du workspace. scoped associe les chemins de sources, de tests, de benchmarks, d’exemples et de manifestes locaux à un paquet, parmi les sources Rust, vers les racines de paquets issues des métadonnées Cargo. Cet ensemble est ensuite étendu aux dépendants inverses du workspace. Les chemins src/ et tests/ de la racine sont associés au paquet racine. Les noms de paquets sont dédupliqués, et zeroclaw-desktop est exclu comme dans la suite existante du workspace. full est sélectionné pour les manifestes du workspace ou la résolution des dépendances, .cargo, la chaîne d’outils Rust, l’infrastructure de CI ou de tests, le sélecteur ou le workflow, les chemins inconnus, les métadonnées ambiguës, et toute autre modification qui ne peut pas être associée de manière sûre. Chaque modification de Cargo.lock sélectionne full, car la résolution des dépendances à l’échelle du workspace peut affecter des paquets au-delà des manifestes directement modifiés. Les modifications directes des paquets racine, gateway ou provider, ainsi que les modifications des chemins de plugin, de runtime, de configuration de plugin, de WIT, d’activation du plugin racine, de filtre de backend de plugin, de dépendances, de sélecteur, de contrat du sélecteur, de ci.yml ou de windows-tests.yml, définissent needs_plugin_host=true. Les cas de fichiers de contrôle font en sorte que les révisions du workflow exécutent le chemin plugin-host qui leur est propre. Une entrée de chemins modifiés malformée ou indisponible sélectionne full de base et true. Des métadonnées Cargo absentes ou malformées sélectionnent aussi full de base avec needs_plugin_host=true, car la fermeture des dépendances ne peut pas être établie de manière sûre.
Pour scoped, le job Windows passe des arguments -p explicites à cargo nextest ; pour full, il exécute cargo nextest run --locked --no-fail-fast --workspace --exclude zeroclaw-desktop. Lorsque needs_plugin_host=true, il installe wasm32-wasip2 et ajoute les cibles de composants zeroclaw-plugins activées par les fonctionnalités (channel_plugin_e2e, tool_plugin_timeout_e2e, reference_plugin, reference_plugin_e2e et tool_plugin_e2e), les tests de bibliothèque des plugins, les régressions du runtime liées à la configuration en direct et à l’admission, les tests de bibliothèque de la gateway, les tests CLI des plugins du binaire racine (le client du registre de plugins, les modules plugins:: incluant les preuves shell Windows de la cérémonie d’egress, les tests CLI de la cérémonie d’egress et les tests de plugin update dans le binaire racine), les tests de composants plugin_update_cli et plugin_info_cli, qui exécutent le binaire réel, ainsi que les cibles racine plugin_channel_runtime_e2e et channel_egress_e2e. Chaque invocation ajoutée s’exécute même si une invocation précédente échoue, et le résumé conserve le statut de chaque phase ainsi que l’inventaire des échecs. Le job signale des durées distinctes pour la base, l’hôte de plugins et le total. Les sélections ordinaires scoped et full n’encourent pas le coût de compilation de l’hôte de plugins. Ce workflow de PR restaure les entrées de cache platform-test compatibles mais ne les sauvegarde jamais. Il effectue le checkout du SHA de fusion de PR figé et ne persiste pas les identifiants de checkout. Un job Windows ignoré est intentionnel et visible à côté du résultat du sélecteur.
Tests de plateforme planifiés (platform-tests.yml)
Exécute cargo nextest run --locked --workspace --exclude zeroclaw-desktop --no-fail-fast sur macos-15 et windows-latest après une vérification de formatage Linux peu coûteuse. Cette exécution nocturne sur l’ensemble du workspace sert de filet de sécurité pour la couverture Windows sélectionnée par label et recense les échecs grâce à --no-fail-fast. La matrice s’exécute pour :
- les pull requests qui modifient
platform-tests.ymllui-même ; - déclenchements manuels ; et
- la planification nocturne à 03:17 UTC.
Les jobs utilisent continue-on-error et n’alimentent pas CI Required Gate. Il s’agit de preuves de portabilité, et non d’exigences de fusion. Les PR de code ordinaires ne lancent pas automatiquement cette matrice complète : les mainteneurs peuvent la déclencher manuellement sur une branche lorsqu’une preuve complète sur toutes les plateformes est utile. Le workflow ne s’exécute pas pour les événements ordinaires push ou merge_group. Les exécutions nightly et les exécutions déclenchées manuellement sur master peuvent écrire des caches de confiance ; les exécutions sur pull request ne le peuvent pas. --no-fail-fast permet de garder visibles tous les échecs de plateforme dans une seule exécution.
Analyse quotidienne (daily-audit.yml)
Exécute cargo deny check advisories quotidiennement à 09:00 UTC sur l’arborescence des dépendances. Ouvre une issue en cas de résultats. Aucune action requise sauf si une vulnérabilité est signalée.
Audit npm quotidien (daily-npm-audit.yml)
Exécute npm audit --audit-level=high quotidiennement à 09:23 UTC sur web/package-lock.json. Ouvre une issue security + dependencies dédupliquée lorsque des avis npm de haute sévérité affectent le lockfile web commité.
Analyse d’image Trivy hebdomadaire (trivy-scheduled.yml)
Analyse les images GHCR dist et default-features publiées chaque samedi et charge les résultats HIGH/CRITICAL dans l’onglet Security au format SARIF. L’analyse est en mode rapport uniquement (exit-code: 0 pour les résultats), mais une image attendue manquante fait échouer le job avant la configuration de Trivy, avec le tag absent et le workflow éditeur propriétaire indiqués dans l’erreur.
Canary hebdomadaire du bucket Scoop (scoop-bucket-canary.yml)
Répète le processus de publication Scoop avec la version stable actuelle chaque lundi. Il résout le dernier tag vX.Y.Z et appelle pub-scoop.yml avec dry_run: true et credential_canary: true, afin de tester le véritable SCOOP_BUCKET_TOKEN sur le véritable bucket sans rien écrire.
credential_canary est la partie de ce contrat qui applique le refus par défaut : l’absence de SCOOP_BUCKET_REPO ou de SCOOP_BUCKET_TOKEN fait échouer l’exécution, et les identifiants configurés doivent parvenir à la sonde d’autorisation git push --dry-run. Une exécution manuelle générique de pub-scoop.yml avec uniquement dry_run: true reste permissive pour la génération du manifeste et peut ignorer cette sonde lorsque les identifiants sont indisponibles ; n’utilisez pas le mode générique comme preuve de vérification des identifiants.
Cela existe parce que SCOOP_BUCKET_TOKEN est lié au compte : il expire et perd silencieusement le droit d’écriture lorsque l’autorisation de collaborateur de l’identité propriétaire sur le bucket change. Les deux cas se sont produits. Avant le canary, le seul processus qui utilisait l’identifiant était le job scoop post-publication ; un jeton invalide n’a donc été découvert qu’après la création et l’annonce de la release, et le bucket a dû être mis à jour manuellement.
Le canari détecte l’obsolescence des identifiants. Il n’est délibérément pas chargé de maintenir le bucket dans un état correct, et il n’est pas intégré à Release Stable : des identifiants expirés de gestionnaire de paquets ne doivent jamais bloquer ni retarder une release.
Comment le bucket Scoop reste correct
Aujourd’hui, le module de publication des versions est le seul composant d’écriture automatisé :
pub-scoop.ymlpublie lors d’une release. Les utilisateurs de Scoop voient immédiatement la nouvelle version lorsque cette opération réussit. Il nécessite le jeton inter-dépôtsSCOOP_BUCKET_TOKEN, qui constitue le point fragile.- Les mainteneurs récupèrent les pushes échoués. Faites tourner ou réparez le jeton, déclenchez Scoop Bucket Canary pour le vérifier via le chemin
credential_canaryen mode fail-closed, relancez le publisher avecdry_run: false, et confirmez que le manifeste du bucket contient la version de la release.
Un Excavator côté bucket est proposé dans scoop-zeroclaw#1. Une fois ce workflow fusionné, que le dépôt du bucket accorde à Actions l’autorisation de lecture/écriture des workflows et qu’un test de fumée effectué par un mainteneur prouve qu’il valide une mise à jour, il pourra devenir une couche de récupération indépendante des identifiants. Tant que ces trois conditions ne sont pas remplies, ne supposez pas qu’un éditeur défaillant se rétablira automatiquement.
Les blocs checkver et autoupdate sont déjà indispensables pour le parcours Excavator prévu. Le parcours de push actuel utilise également scripts/release/scoop_metadata.sh pour déduire le modèle d’URL de publication à partir de autoupdate, de sorte que les deux parcours partagent un même contrat de manifeste. Ne supprimez pas ces blocs et ne les retirez pas manuellement de dist/scoop/zeroclaw.json.
Étiqueteur de chemin de PR (pr-path-labeler.yml)
Applique automatiquement les libellés de chemin et de portée en fonction des fichiers modifiés. Il s’exécute à l’ouverture, à la réouverture et à chaque mise à jour poussée sur la branche de la PR. Comme sync-labels: true est activé, les libellés définis dans .github/labeler.yml sont recalculés à partir de l’ensemble actuel des fichiers de la PR.
Ce workflow n’applique pas les labels risk:*, size:*, type:*, de niveau de contributeur, de statut, de résolution, stale ou pickup. Si une PR n’a pas de label de chemin/périmètre, vérifiez si les chemins de .github/labeler.yml couvrent les modifications.
Dependabot dispose d’une configuration d’étiquettes distincte dans .github/dependabot.yml pour ses propres PR. Les PR de mise à jour Cargo commencent par dependencies ; les PR de mise à jour GitHub Actions et Docker commencent par ci et dependencies.
Étiqueteur de taille de PR (pr-size-labeler.yml)
Applique exactement un label canonique size:* à partir des métadonnées de fichiers de la PR. Il s’exécute à l’ouverture, à la réouverture et à chaque mise à jour poussée sur la branche de la PR. Le classificateur compte les ajouts plus les suppressions après avoir exclu les fichiers de type documentation et Cargo.lock, puis applique le tableau de seuils de Labels.
Ce workflow s’exécute dans pull_request_target afin de pouvoir écrire des labels sur les PR provenant de forks, mais il récupère le script de classification depuis la révision de confiance du workflow/de la branche par défaut. Il n’effectue aucun checkout, build, import, source ni exécution de code de la pull request. Il n’applique aucun des champs risk:*, type:*, contributor-tier, status, resolution, stale, pickup ni ProjectV2.
Rapport de risque des PR (pr-risk-labeler.yml)
S’exécute lors des mises à jour de PR et des changements de labels risk/security via pull_request_target, ainsi que par déclenchement manuel pour une seule PR. Il récupère le classifieur et la politique compatible JSON depuis la révision de workflow de confiance, lit les métadonnées de PR, les fichiers modifiés, les labels et le texte source de l’API uniquement lorsque la preuve de test uniquement de #9530 en a besoin, et écrit un résumé d’étape lisible par un humain ainsi que des preuves au format JSON. Il ne fait jamais de checkout, ne compile, n’importe, n’exécute via source ni n’exécute de code de pull request.
La phase 2 est en mode rapport uniquement : le workflow dispose des permissions contents: read et pull-requests: read, n’ajoute, ne supprime ni ne remplace aucun label, ne crée aucun statut de commit, n’impose pas de nombre minimum d’approbations et n’exige pas de ruleset de dépôt. risk:manual est signalé comme un gel strict pour toute future modification, tandis que domain:security est signalé indépendamment du résultat risk:* proposé.
Le classificateur attribue par défaut un niveau supérieur lorsque les preuves sont incertaines. Les documents, les fixtures et les métadonnées mécaniques connues peuvent être risk:low ; les modifications de comportement ordinaires sont risk:medium ; les chemins configurés de confiance, d’identifiants, de compatibilité, de gouvernance, d’autorité de publication, de sécurité ou de plancher de chaîne d’outils sont risk:high. La politique versionnée comprend également des règles d’escalade déterministes sur les lignes modifiées : extension des permissions de workflow, accès aux secrets, OIDC, publication d’artefacts, comportement des releases, pull_request_target élevé, installation de la chaîne d’outils ou bases de conteneurs, modifications du plancher de release et contrats WIT probables. Si l’un de ces fichiers sensibles au contenu change alors que le patch est indisponible ou malformé, le rapport reste à risque élevé au lieu de deviner un risque moyen. L’exception #9530 ne peut recommander risk:medium que si un patch Rust complet et des sources base/head correspondantes prouvent que chaque modification Rust à haut risque reste dans du code #[cfg(test)] existant ; les preuves manquantes, tronquées, malformées, mixtes, de compilation conditionnelle, de renommage, d’ajout ou de suppression restent à risque élevé.
Planificateur de tableau de bord de projet (project-dashboard-plan.yml)
S’exécute manuellement pour un numéro d’issue unique. Il lit l’état et les labels de l’issue, puis écrit un résumé d’étape purement informatif proposant la valeur existante de Project Status qui correspond le mieux à l’issue.
Ce workflow ne s’exécute pas automatiquement sur les événements d’issues, n’écrit pas dans les champs ProjectV2, ne modifie pas les issues, n’ajoute pas de libellés, ne publie pas de commentaires et ne recalcule pas les libellés risk:* ou type:* des PR. Une mutation ProjectV2 en direct ou une planification automatique sur les événements d’issues nécessite un mappage des champs, une politique de déclenchement et un identifiant limité au projet, tous approuvés séparément.
Valider le titre de la PR (pr-title.yml)
S’exécute à chaque ouverture/modification/synchronisation de PR. Exécute les tests unitaires du validateur (scripts/check-pr-title.test.sh) et vérifie que le titre de la PR respecte les Conventional Commits (scripts/check-pr-title.sh).
Déployer la documentation mdBook sur Pages (docs-deploy.yml)
Déclenché lors d’un push de tag (et via workflow_dispatch) ; compile et publie la documentation versionnée sur la branche gh-pages. Voir Release Runbook → Versioned documentation deployment pour les règles de version minimale et d’amorçage.
Vérification PR de l’image Docker (docker-image-pr.yml)
S’exécute uniquement lorsque les fichiers de l’image Docker, de Compose ou du contexte Docker de publication changent. Il valide la configuration Compose fusionnée par défaut et Alpine et, pour les modifications allant au-delà des seules modifications de Compose, construit les images de test préconstruites par défaut et Debian ainsi que les Dockerfiles sources, sans les publier. Lorsque la seule modification sur le chemin Docker concerne release-stable-manual.yml, il ne construit que les images de test préconstruites : le job Docker du workflow de publication empaquette des binaires préconstruits via scripts/ci/prepare_docker_context.sh, que ces images exercent, et il ne consomme jamais les Dockerfiles sources. Une modification qui touche également à une entrée de l’image source (un Dockerfile, le Containerfile, les fichiers Compose, .dockerignore, le modèle de configuration, les scripts de contexte Docker ou ce workflow) construit tout de même la matrice complète des images sources. Les images sources par défaut et Alpine sont construites pour linux/amd64 et linux/arm64 ; l’image source Debian est construite pour linux/amd64. Des voies linux/amd64 distinctes pour Alpine et Debian activent plugins-wasm-runtime-only afin que leurs contextes de construction prouvent en continu que le contrat WIT du dépôt est disponible pour les builds sources avec plugins activés.
L’image source Containerfile avec toutes les fonctionnalités se construit pour linux/amd64 lorsque ce fichier ou le workflow Docker change. Elle utilise un périmètre de cache isolé et n’est ni chargée ni poussée. La voie Alpine amd64 exécute les deux binaires, démarre l’image construite à partir de la configuration Compose fusionnée, et vérifie la santé de la passerelle ainsi que les surfaces du tableau de bord. La voie Alpine arm64 ne couvre que la compilation et l’assemblage de l’image. Elle effectue une compilation croisée sur blacksmith-8vcpu-ubuntu-2404, la classe de runners Rust lourds existante de 32 Go, parce que l’édition de liens de la version a épuisé la mémoire sur ubuntu-latest. Les autres jobs Docker restent sur ubuntu-latest ; l’optimisation de release, les options de build et la taille de la matrice ne changent pas. Les modifications de Compose uniquement utilisent une matrice Alpine amd64 réduite, afin de continuer à tester le contrat d’exécution sans reconstruire les images non concernées. Tous les jobs disposent d’un accès en lecture seule au dépôt et d’aucune permission d’écriture sur le registre.
Publication Docker (docker-publish.yml)
Construit, signe et analyse la matrice à quatre variantes générée depuis dev/ci/docker-tags.toml : minimal, default-features, dist et all-features. Un tag v* créé manuellement déclenche ce workflow directement. Une release stable démarrée avec workflow_dispatch crée son tag via GITHUB_TOKEN, ce qui n’émet pas de nouvel événement de push de tag ; ainsi, release-stable-manual.yml appelle Docker Publish de manière synchrone sur le tag de release immuable une fois que les jobs de release canonique et Docker ont réussi.
Cette matrice complète plutôt qu’elle ne remplace les images préconstruites latest, versionnées et debian de la version stable. Les deux chemins utilisent des entrées de build différentes et publient des tags distincts.
Publication Discord (discord-release.yml)
Se déclenche après une version stable réussie. Publie les notes de version sur le Discord communautaire.
Publication de tweet (tweet-release.yml)
Se déclenche après une version stable réussie. Publie un tweet d’annonce.
Vérification hebdomadaire de la fraîcheur d’AUR (aur-freshness-check.yml)
Compare la version AUR publiée de zeroclawlabs à la version stable actuelle publiée sur GitHub chaque lundi, et échoue si l’AUR est en retard.
La publication vers l’AUR fonctionne sans suivi : si pub-aur.yml échoue, aucune nouvelle vérification n’est effectuée, et le paquet prend donc du retard sans que personne ne s’en aperçoive. C’est exactement ce qui s’est produit après v0.8.4. Une fenêtre de maintenance de aur.archlinux.org a coïncidé avec la publication ; l’unique clonage, qui n’a fait l’objet d’aucune nouvelle tentative, a échoué avec The AUR is down due to maintenance, et le paquet est resté trois semaines en retard sans aucun signalement. Le système de publication n’autorise désormais pas plus d’une publication active réelle et effectue des nouvelles tentatives pour résister à une brève interruption ; GitHub peut remplacer une publication réelle mise en file d’attente plus tôt dans le même groupe de concurrence, tandis que les exécutions à blanc utilisent un groupe distinct. À chaque tentative, l’état de référence du paquet est cloné à nouveau, et le système refuse de remplacer un tuple epoch:pkgver-pkgrel plus récent par un autre plus ancien. Un budget de nouvelles tentatives ne peut toujours pas couvrir tous les échecs ; ce contrôle constitue donc le garde-fou qui rend visible un échec passé inaperçu ou une exécution remplacée.
Si le RPC de l’AUR est inaccessible, la vérification émet un avertissement et réussit au lieu d’échouer. Une panne de l’AUR est un problème de disponibilité en amont, et non d’obsolescence des paquets, et la prochaine exécution planifiée effectue une nouvelle vérification. L’obsolescence est durable, donc une détection retardée est acceptable ; une page hebdomadaire consacrée à la fenêtre de maintenance de quelqu’un d’autre ne l’est pas.
La documentation est générée et publiée dans le cadre du pipeline de publication plutôt qu’à chaque push sur master. La traduction est un workflow local uniquement pour les PR dédiées au cache de traduction, les nouvelles locales et les passes de traduction de publication. Les PR de documentation courantes en anglais peuvent différer les nombreuses modifications générées dans les fichiers .po. Consultez Documentation et traductions pour les recommandations aux contributeurs et le guide opérationnel de publication pour la procédure de publication.
Flux de travail manuels et consultatifs
Analyse mensuelle des obsolétés (monthly-outdated.yml)
Scan mensuel planifié le 1er de chaque mois à 09:00 UTC. Exécute cargo outdated --workspace sur tous les membres du workspace. Ouvre une issue dependencies-labeled lorsque des deps obsolètes sont trouvées. Permissions : contents: read + issues: write. La garde de déduplication empêche l’accumulation si l’issue précédente est toujours ouverte.
Le aperçu du ticket peut être tronqué, mais l’artefact lié monthly-outdated-report conserve la sortie complète de l’analyse pendant 30 jours.
Première étape de triage pour une nouvelle issue : vérifiez si les crates obsolètes signalées présentent des changements de version incompatibles avec semver et si l’API de la crate consommatrice a changé. Si ce changement est trivial (patch/mineur), créez une PR courte portant uniquement sur les dépendances. Si la mise à niveau est bloquée par des cassures de compatibilité semver, fermez l’issue avec une note et le nom de la crate bloquante.
Génération multiplateforme (cross-platform-build-manual.yml)
Déclenchement manuel de la compilation des binaires de publication pour l’ensemble de la matrice de cibles : Linux x86_64/aarch64 GNU et MUSL, ainsi que armv7 et arm hard-float, macOS Intel/ARM, Windows x86_64 et aarch64-linux-android (compilé avec le NDK). Utilisez ceci pour vérifier qu’une branche se compile sans erreur sur les cibles non Linux avant de créer le tag.
Chaque déclenchement exécute également une petite matrice de tests de fumée des outils de release, indépendamment des builds. Définissez release_tools_only lorsque seules ces vérifications sont nécessaires ; les jobs web et release-build sont alors ignorés. Sur Linux x86_64 hébergé par GitHub et de confiance, le test de fumée installe l’archive de cross dans la version épinglée, confirme la présence de cross et de cross-util, puis consigne cross --version. Sur Windows x86_64 hébergé par GitHub et de confiance, il utilise la même version de Rust et la même structure de chemin de Bash vers Cargo que le workflow de release stable, puis consigne cargo-tauri.exe --version et cargo tauri --version. Chaque exécution de la matrice consigne le commit exact testé et l’architecture du runner dans le résumé public du job. Le test de fumée utilise des autorisations en lecture seule sur le dépôt et ne comporte aucun job de publication, environnement, secret ni téléversement d’artefact.
Les jobs de compilation MUSL installent également cross via scripts/ci/install_release_tool.sh, qui télécharge l’artefact de version amont exact épinglé et en vérifie la somme SHA-256 avant de l’installer. Le job obligatoire Repository Structure teste la correspondance prise en charge entre le runner et l’artefact, ainsi que le contrat du workflow smoke, sans effectuer d’appels réseau.
La valeur par défaut qualification_profile: normal conserve la matrice complète, la route de fumée binaire brute dist-broad, la route d’archive standard dist, les politiques de cache normales, ainsi que le cycle de vie mémoire ARM64 existant et le plafond macOS de 180 minutes. Ses cinq jobs conservent CARGO_INCREMENTAL=0 au niveau du job. Le test de fumée de l’outil de publication valide les entrées de dispatch avant d’installer les outils ; les jobs produit normaux conservent leur ordonnancement indépendant.
Sélectionnez qualification_profile: stable-gnu-ubuntu22 avec distribution: dist et release_tools_only: false pour une qualification finie et sans publication. Les combinaisons vides, inconnues, malformées, à distribution large ou limitées aux outils sont refusées. Quatre lignes de build GNU s’exécutent sur Ubuntu22 : x86_64, aarch64, armv7 et arm. Elles appellent la même action locale build-release-payloads que Stable, avec ses liaisons existantes pour l’éditeur de liens et ARMv6, Rust1.98, profil de release verrouillé, fonctionnalités canoniques explicites, jobs Cargo ordinaires, trois charges utiles par ligne, plafond de 90 minutes et plafond inclusif de 83,886,080 octets pour le binaire principal. Seul le qualificatif active la sortie des fonctionnalités de l’action ; son manifeste consomme cette résolution unique. Stable laisse le commutateur de preuves désactivé.
Les producteurs web et fixtures distincts utilisent les points d’entrée existants et la configuration de toolchain Stable. Ces trois rôles de build appellent directement l’action Swatinem épinglée avec les clés partagées d’origine web, plugin-smoke-packages et build. Le préfixe fixture et chaque préfixe de cible Ubuntu22 correspondent à ceux de Stable, et la restauration de la cible par défaut reste activée. Chaque cache qualifier utilise save-if: false, y compris sur master. Ces rôles omettent la déclaration CARGO_INCREMENTAL au niveau du workflow/job, comme pour Stable. L’action de cache épinglée exporte CARGO_INCREMENTAL=0 avant de construire sa configuration de cache et conserve cette valeur dans les étapes suivantes. Les reçus des producteurs exigent et enregistrent la chaîne effective "0", et les contrôles de complétude vérifient cette même valeur. La valeur exportée par l’action est conservée. La politique de cache web normale est inchangée. Les journaux de l’action doivent fournir la clé primaire réelle, les clés de restauration, l’indicateur hit et la source du cache avant que la parité des caches puisse être acceptée. Ces entrées ne garantissent ni un cache chaud, ni une durée de build Stable ultérieure.
Les deux consommateurs natifs exécutent x86 GNU sur Ubuntu22 et ARM64 GNU sur Ubuntu24-arm, avec des plafonds de 30 minutes. Chacun vérifie et extrait son archive de la même exécution, lance la suite verbeuse complète du fournisseur scripté dans un délai de cinq minutes, et invoque sans condition le test de fumée du plugin sensible aux archives. Il n’existe aucune solution de repli brute. Le job de complétude sélectionné s’exécute même après un échec en amont et rejette chaque rôle manquant, ignoré, en échec ou annulé. Il contrôle quatre archives avec le vérificateur inchangé, douze empreintes des fichiers d’entrée et leur ELF sauvegardé, les observations de compilateur et de sysroot ainsi que des ressources, et deux supports de preuves natifs. Les empreintes du commit source, de l’arborescence, de l’exécution, de la tentative, du profil, du web et des fixtures lient les reçus à un même dispatch.
Avant d’accepter cette route, conservez les reçus de cache réels, interprétez les douze observations ABI et inspectez les treize tests de fournisseur nommés et les dix-huit contrôles d’archive de chaque carrier natif. Un job réussi ou un inventaire des carriers ne remplace ni ces résultats ni la revue du mainteneur. Des preuves manquantes ou non interprétables suspendent l’acceptation ; elles n’autorisent aucun changement automatique de budget, de ressource, de fonctionnalité ou de politique ABI. L’exécution sur Ubuntu24 ARM64 ne constitue pas une preuve d’exécution à l’exécution sur Ubuntu22 ARM64. ARM32, l’exécution des compagnons, Darwin, MUSL, Android, Windows, la matrice complète de Stable et l’état de préparation à la publication exigent chacun leurs propres preuves. Une modification de la source seule ne prouve aucun de ces résultats d’exécution.
Clippy multiplateforme (cross-platform-clippy.yml)
Couverture manuelle et hebdomadairement planifiée du linter d’avis sur les cibles macOS aarch64 et Windows x86_64. Elle correspond à la commande de linter PR requise avec --target défini pour chaque plateforme, mais ne s’exécute pas intentionnellement sur les PR et ne fait pas partie de CI Required Gate.
Clippy Linux obligatoire, Clippy multiplateforme à titre indicatif et Clippy Windows ciblé appellent scripts/ci/run_clippy.sh. Cet exécuteur prend en charge les formats de commande pris en charge, la propagation des codes de sortie de Cargo ainsi que les diagnostics partagés relatifs à la durée, au cache, au nombre de compilations et au nombre de téléchargements. Les fichiers de workflow continuent de gérer les déclencheurs, les exécuteurs, les chaînes d’outils, les caches, les délais d’expiration et l’inclusion dans les contrôles obligatoires.
Version Stable (release-stable-manual.yml)
Déclenchement manuel du pipeline de publication complet. Compile toutes les cibles, crée la GitHub Release, pousse les images Docker préconstruites latest, versionnées et debian vers GHCR, appelle la matrice générée des variantes Docker sur le tag de publication, déclenche le redéploiement du site web et invoque les sous-workflows de distribution (Scoop, AUR, Discord, tweet). Homebrew Core détecte les nouvelles versions via son propre service autobump. Deux barrières d’environnement nécessitent l’approbation d’un mainteneur en cours d’exécution : github-releases (le job publish) et docker.
Les ressources téléchargeables utilisent des attestations Build Level 2 hébergées par GitHub. Les bundles hors ligne et le matériel de racine de confiance sont fournis dans une seule archive de vérification, et les deux formats SBOM font l’objet d’une somme de contrôle et d’une attestation avant la création de la version. Cosign reste limité à la signature des images GHCR.
Chaque archive 64 bits, à l’exception de l’archive Android expérimentale, embarque l’hôte de plugins WASM. Un release déclenché manuellement valide cet hôte avant la création de la GitHub Release et avant les publications de ses propres images. Le job plugin-smoke-packages compile une fois les paquets de plugins de test (fixtures) sous Linux, puis le job plugin-smoke télécharge chaque archive sur un runner de sa propre plateforme et exécute scripts/ci/plugin_artifact_smoke.sh sur le binaire empaqueté (le même script que lance le job de test de fumée check-plugin-backends sur les pull requests). Les jobs publish et docker attendent la fin de tous les jobs. Un test xtask garantit que la liste des jobs correspond à la politique de plugins par cible définie dans le registre : une cible ne peut donc pas embarquer l’hôte sans job de test, ni perdre son job alors qu’elle l’embarque encore. Les jobs de compilation échouent si un binaire dépasse le plafond de 80 MiB défini dans l’étape de vérification de la taille. Le workflow manuel Cross-Platform Build répète les mêmes jobs sur les binaires qu’il compile, de sorte qu’une modification de distribution est mesurée et validée en un seul déclenchement manuel, avant qu’un release ne soit lancé. Un release démarré par un push de tag déclenche également Docker Publish via son propre déclencheur sur tag v*, et cette exécution n’attend pas le test de fumée.
Consultez le Runbook de publication pour la procédure complète.
Les outils de compilation utilisés uniquement pour les releases ne sont pas compilés depuis les sources à chaque exécution. Le workflow installe les binaires de release en amont épinglés de cross et de Tauri CLI via scripts/ci/install_release_tool.sh ; ce script vérifie le SHA-256 stocké dans le dépôt pour chaque archive propre au runner avant de placer le binaire dans le répertoire bin de Cargo. La mise à jour de l’un ou l’autre outil nécessite de mettre à jour simultanément sa version, le nom de son artefact et sa somme de contrôle, puis d’exécuter scripts/ci/install_release_tool.test.sh.
Éditeurs de paquets
Chacun se déclenche sur workflow_dispatch avec un paramètre de version. Ils sont également invoqués depuis le workflow de publication après une publication réussie.
| Flux de travail | Ce que cela fait |
|---|---|
pub-aur.yml | Met à jour le PKGBUILD de l’Arch User Repository et le pousse vers l’AUR |
pub-crates.yml | Empaquette et vérifie la version coordonnée de l’espace de travail, puis la publie sur crates.io dans l’ordre des dépendances, derrière la barrière d’environnement crates-io |
pub-scoop.yml | Met à jour le manifeste Scoop pour Windows |
Le service officiel d’autobump de Homebrew Core découvre les versions stables GitHub et ouvre des mises à jour de formules de manière indépendante. Ne restaurez pas un éditeur Homebrew appartenant au projet ni un jeton de fork ; cela duplique l’automatisation faisant autorité de Homebrew.
Secrets requis
| Secret | Utilisé par |
|---|---|
AUR_SSH_KEY | pub-aur.yml |
CARGO_REGISTRY_TOKEN | Secret du dépôt transmis explicitement à pub-crates.yml et référencé uniquement par son job de publication protégé ; la v0.8.5 nécessite publish-new pour zerorelay, zeroclaw-relay-proto et zeroclaw-tls, tandis que les mises à jour coordonnées ultérieures nécessitent publish-update |
DISCORD_WEBHOOK_URL | discord-release.yml |
TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET, TWITTER_CONSUMER_API_KEY, TWITTER_CONSUMER_API_SECRET_KEY | tweet-release.yml |
SCOOP_BUCKET_TOKEN | pub-scoop.yml, release-stable-manual.yml, scoop-bucket-canary.yml ; PAT à granularité fine limité à zeroclaw-labs/scoop-zeroclaw avec Contents en lecture/écriture |
WEBSITE_REPO_PAT | release-stable-manual.yml (déclenche le redéploiement du dépôt du site web) |
GITHUB_TOKEN (automatique) | Tous les workflows qui poussent des commits, ouvrent des PR ou poussent des images vers GHCR |
Les images Docker sont poussées vers GHCR à l’aide du GITHUB_TOKEN automatique ; il n’existe aucun jeton de registre distinct. Stockez CARGO_REGISTRY_TOKEN en tant que secret du dépôt et ne transmettez que ce secret nommé au workflow de publication réutilisable. Le workflow appelé ne le référence que dans l’étape de publication irréversible, dont le job nécessite une approbation via l’environnement crates-io ; la pré-vérification sans jeton ne le référence ni ne l’exporte. La pré-vérification empaquette le même commit immuable de la version avant qu’un approbateur puisse démarrer le job de publication.
Release Stable appelle le publieur deux fois via son entrée stage. Le job crates-preflight exécute stage: preflight en parallèle des builds des binaires, sans secret. Une release déclenchée manuellement n’a pas encore de tag, donc elle vérifie directement le commit de l’exécution. Le job publish de la GitHub Release en dépend ; un échec de crates interrompt donc la release avant que quoi que ce soit ne soit public. Après la GitHub Release, le job crates exécute stage: publish avec le token. Cette étape exige que le nouveau tag corresponde au même commit, ignore la re-vérification et téléverse le bundle du tableau de bord dont le digest a été enregistré par l’étape preflight. Un déclenchement manuel de pub-crates.yml exécute toujours les deux moitiés en un seul appel.
Les deux jobs exécutent les scripts de publication depuis un second checkout partiel (sparse) de scripts/release/ dans .release-tooling, exclu de l’arborescence empaquetée, et transmettent l’arborescence via PUBLISH_SOURCE_ROOT. Dans une exécution de publication, ce checkout correspond au commit de publication. Un déclenchement manuel depuis master pour un tag existant utilise les scripts de master sur l’arborescence taggée, de sorte qu’un correctif du publieur peut atteindre une publication sans déplacer son tag. resolve_crates_release.sh rejette tout autre commit d’outillage.
La crate racine livre le tableau de bord généré sous web/dist. Le preflight le construit une seule fois, enregistre un condensé de chaque chemin de fichier et de son contenu, vérifie les paquets avec cette arborescence, puis le téléverse en tant qu’artefact crates-io-web-dist. Le job de publication ne reconstruit pas le tableau de bord. Il restaure cet artefact, et publish-crates.sh refuse d’atteindre crates.io tant que l’arborescence restaurée ne correspond pas au condensé enregistré. L’artefact est conservé pendant 30 jours afin qu’une approbation tardive ou une relance du job de publication échoué le retrouve encore.
La plupart des crates de l’ensemble de publication coordonnée existent déjà et sont éligibles à la publication approuvée sur crates.io. La version v0.8.5 crée en plus zerorelay, zeroclaw-relay-proto et zeroclaw-tls; son jeton d’amorçage doit donc inclure publish-new. Le jeton d’environnement reste la méthode d’amorçage jusqu’à ce que chaque crate dispose d’une entrée de publication approuvée pour ce flux de travail. Une fois ces entrées configurées, migrez le job afin que GitHub échange l’identité OIDC contre un jeton à courte durée de vie au lieu de conserver CARGO_REGISTRY_TOKEN.
L’organisation désactive actuellement les clés de déploiement sur le bucket Scoop, et le GITHUB_TOKEN automatique ne peut pas écrire dans un autre dépôt. Limitez la portée de SCOOP_BUCKET_TOKEN strictement au bucket ; ne réutilisez pas le jeton CLI étendu d’un mainteneur. Le publieur vérifie l’accès en écriture avec git push --dry-run, puis utilise le même transport Git pour la mise à jour réelle.
Rotation de SCOOP_BUCKET_TOKEN
Comme les clés de déploiement ne sont pas disponibles, cet identifiant d’authentification est un jeton d’accès personnel et présente donc deux modes de défaillance indépendants, qui ont tous deux déjà perturbé une mise en production :
- Le jeton expire. Les PAT à granularité fine ont une durée de vie maximale ; ce problème se reproduit donc selon une périodicité fixe, indépendamment de tout autre changement.
- L’identité propriétaire perd le droit d’écriture sur le bucket. Le token peut toujours être valide alors que le compte qui lui est associé n’est qu’un collaborateur
read. Cela produitremote: Permission to zeroclaw-labs/scoop-zeroclaw.git denied to <account>et HTTP 403, et non une erreur d’authentification ; le problème semble donc venir du code alors qu’il s’agit d’un problème d’autorisations.
Associez le token au compte ZeroClaw-Bot, jamais à un compte personnel, afin que le processus de publication ne dépende pas des identifiants d’un seul responsable de maintenance. Pour effectuer une rotation :
- En tant que
ZeroClaw-Bot, créez un PAT à granularité fine avec Propriétaire de la ressourcezeroclaw-labs, Accès aux dépôts limité au seul dépôtzeroclaw-labs/scoop-zeroclaw, et Autorisations du dépôt → Contenu : Lecture et écriture. Rien d’autre. - Confirmez que l’organisation a approuvé le jeton. Les PAT à granularité fine dont le propriétaire de ressources est une organisation restent en attente jusqu’à leur approbation, et un jeton en attente permet de s’authentifier, mais pas d’effectuer un push.
- Confirmez que
ZeroClaw-Botdispose toujours de l’autorisationwritesur le bucket :gh api repos/zeroclaw-labs/scoop-zeroclaw/collaborators/ZeroClaw-Bot/permission --jq '.role_name'. L’étape 1 n’accorde pas d’accès au dépôt ; elle définit uniquement ce que le jeton peut utiliser. Un jeton ne peut pas dépasser les autorisations dont dispose déjà son propriétaire. - Définissez le secret :
gh secret set SCOOP_BUCKET_TOKEN --repo zeroclaw-labs/zeroclaw. - Vérifiez sans toucher au bucket en déclenchant Scoop Bucket Canary. Une exécution réussie prouve que le nouveau jeton peut effectuer un push.
Notez la date d’expiration dans un emplacement durable lors de la rotation. Le canary détectera de toute façon un jeton expiré dans un délai d’une semaine, mais seulement après qu’il aura déjà cessé de fonctionner.
Propriété du paquet AUR
Le paquet appartenant au projet est actuellement zeroclawlabs, maintenu par zeroclaw-bot. Le paquet portant le nom canonique zeroclaw est un paquet tiers et ne peut pas être récupéré en effectuant une rotation de AUR_SSH_KEY. Si ce mainteneur reste inactif, suivez la procédure de demande d’orphelinage AUR avant de modifier pkgname ou la cible de clonage du workflow. Une fois la propriété transférée, coordonnez le renommage ou la fusion du paquet en une seule modification révisée.
Comportement du cache de construction
La plupart des jobs Rust lourds de ci.yml mettent en cache via le composite local ./.github/actions/rust-cache, qui reçoit use-blacksmith: 'true' de ses appelants de compilation Blacksmith, correspondant au label de runner que ces jobs nomment directement. Les deux valeurs sont des constantes versionnées dans ci.yml, et non une variable Actions. vars est illisible dans les exécutions pull_request provenant d’un fork ; l’ancien interrupteur CI_USE_BLACKSMITH envoyait donc silencieusement chaque PR de fork (90 % des PR de ce dépôt) vers des runners hébergés par GitHub avec un cache froid, car master avait cessé d’alimenter le cache GitHub Actions lors de la migration vers Blacksmith. Avec ces constantes, les jobs de PR de fork utilisent useblacksmith/rust-cache (disque persistant NVMe sticky de Blacksmith) sur les runners Blacksmith, comme les jobs correspondants de master. Modifier le label et les entrées du fournisseur sur les jobs de compilation Linux, que tests/architecture/ci_runner_labels.rs maintient synchronisés, fait revenir cette flotte vers ubuntu-latest + Swatinem/rust-cache en un seul commit relu. Les deux références d’actions figurent dans le composite quelle que soit la sélection ; elles doivent donc rester toutes deux dans la liste d’autorisation. La récupération Windows du propriétaire de tâche est l’exception qui utilise directement Swatinem/rust-cache sur Blacksmith, avec son propre cache de dépendances alimenté par master. Les jobs de build macOS et Windows restent sur Swatinem/rust-cache avec le cache GitHub Actions, et les jobs fmt, nix-eval et docs-style (qui ne compilent aucun workspace) n’utilisent aucun cache Rust. codeql.yml épingle le même label Blacksmith pour son scan Rust ; aucun workflow ne lit donc plus la variable CI_USE_BLACKSMITH, qui peut être supprimée des paramètres du dépôt. Ces comportements sont utiles à connaître pour trier les instabilités liées au cache :
- Les jobs Matrix doivent avoir des clés de cache distinctes. Une clé de cache est dérivée de l’identifiant du job (ou de
shared-key), du système d’exploitation et de l’architecture du runner, du toolchain, des variables d’environnementCARGO*/RUST*et du lockfile. Les variantes de matrice qui ne diffèrent que par leurs feature flags ou leurs arguments cargo résolvent donc vers la même clé : la variante qui sauvegarde en premier en est propriétaire, et toutes les autres restaurent un ensemble de dépendances incompatible. Avant l’introduction de clés par variante, la variante cranelift plugin-backend recompilait environ 620 crates de dépendances (environ quatre minutes) à chaque exécution, sur master compris, car elle partageait une clé avec les variantes pulley et runtime-only.checketcheck-plugin-backendsdonnent donc à chaque variante un champ de matricecache-keysans virgule, transmis commeshared-key; les variantes dont les ensembles de dépendances correspondent réellement peuvent partager une même clé. Un nouveau job de matrice Rust devrait faire de même. - Les écritures dans le cache sont réservées à master.
save-ifest conditionné pargithub.ref == 'refs/heads/master', donc les exécutions de PR lisent le cache alimenté par master mais ne le mettent jamais à jour. Les branches de PR ne peuvent pas polluer le cache partagé avec des artefacts spécifiques à une branche. Le déclencheurpushsurmasterest ce qui donne au workflow une exécution de confiance capable d’écrire dans le cache après les fusions. - La récupération n’enregistre qu’en cas de succès. Son
cache-on-failure: 'false'empêche les exécutions de récupération échouées d’alimenter son cache. Les autres tâches de compilation du cache composite conserventcache-on-failure: true, de sorte qu’une exécution partielle sur master peut tout de même alimenter leur prochaine tentative avec un cache chaud. - Le cache de build Windows est activé. L’étape de build Windows exécute la même action de cache Rust épinglée que Linux et macOS. Si le comportement du cache Windows devient instable ou régresse, annulez la modification du workflow et documentez les preuves d’échec de restauration/sauvegarde dans le ticket consacré au cache.
- La compilation incrémentale est désactivée.
CARGO_INCREMENTAL: 0au niveau du workflow. Les builds incrémentaux augmentent la taille du cache et produisent des artefacts non reproductibles dans des conditions de partielle périmée. cargo-denyetcargo-nextestsont réinstallés à chaque exécution. Le jobsecurityexécutecargo install cargo-deny --locked; le jobtestLinux et les deux variantes planifiées deplatform-tests.ymlrécupèrent le binairecargo-nextestapproprié depuisget.nexte.st. Le job Windows de la PR advisory télécharge à la place l’archive de sa version figée de nextest et vérifie un SHA-256 codé en dur avant l’extraction, car il s’exécute sur un ensemble beaucoup plus large de PR non fiables. Aucun des deux outils n’est mis en cache, si bien que chaque installation ajoute un coût fixe à son job. Passer l’un ou l’autre àtaiki-e/install-actionpermettrait de les mettre en cache, mais cette action ne figure pas aujourd’hui dans la liste autorisée.
Lorsque le feu passe au rouge
| Symptôme | Première chose à vérifier |
|---|---|
Release Stable s’arrête à startup_failure avec zéro job après la modification d’une réf uses: | Consultez le récapitulatif de l’exécution et la politique Actions du dépôt. Si GitHub signale un rejet de type selected-actions, comparez la référence modifiée avec la liste d’autorisation, ajoutez uniquement le motif rejeté, attendez la propagation des paramètres, puis lancez une nouvelle exécution. Sinon, examinez la définition du workflow ou une autre politique du dépôt ; startup_failure seul n’identifie pas la cause |
CI Required Gate rouge | Commencez par fmt, puis lint, puis test, puis build |
| La validation de la version a échoué | La version de Cargo.toml ne correspond pas à l’entrée du workflow, ou la balise existe déjà. |
| La phase de build de la version de production a échoué. | Le journal d’exécution de la cible spécifique. Android est expérimental et s’exécute avec continue-on-error. |
| Le délai d’attente de la porte d’environnement est dépassé | Relancer uniquement la tâche ayant expiré depuis la page d’exécution du workflow |
| Échec de l’éditeur de distribution | Relancez manuellement le sous-flux de travail correspondant avec dry_run: true en premier. |
Actions autorisées
Le dépôt exécute les Actions en mode selected, seules les actions de cette liste d’autorisation peuvent s’exécuter. La liste d’autorisation doit rester restreinte ; les nouvelles actions tierces nécessitent l’approbation explicite d’un mainteneur avant d’être ajoutées.
Toutes les références tierces sont épinglées à un SHA de commit complet avec un commentaire de version en fin de ligne ; la colonne de version ci-dessous reprend ce commentaire.
| Action | Utilisé dans | Objectif |
|---|---|---|
actions/checkout (v6.0.2) | La plupart des workflows | Extraction du dépôt |
actions/cache (v4.2.3, v5.0.5) | docker-image-pr.yml, tweet-release.yml | Mise en cache des dépendances génériques et de la base de données Trivy |
actions/setup-node (v7.0.0) | ci-sbom.yml, ci.yml, cross-platform-build-manual.yml, daily-npm-audit.yml, pub-crates.yml, release-stable-manual.yml | Chaîne d’outils Node pour la génération de SBOM npm, les tests et l’audit web, ainsi que les builds web/de bureau |
actions/upload-artifact (v7.0.1) | release-stable-manual.yml, cross-platform-build-manual.yml, docker-publish.yml, trivy-scheduled.yml | Téléverser les artefacts de build et les artefacts de handoff SARIF Trivy |
actions/download-artifact (v8.0.1) | release-stable-manual.yml, cross-platform-build-manual.yml, docker-publish.yml | Télécharger les artefacts de build et les artefacts de transfert SARIF Trivy |
actions/attest (v4.2.2) | release-stable-manual.yml | Générer une provenance de build de niveau 2 hébergée par GitHub pour les ressources de release |
actions/labeler (v6.1.0) | pr-path-labeler.yml | Appliquez les étiquettes de chemin/portée depuis .github/labeler.yml |
dtolnay/rust-toolchain (stable, v1) | ci.yml, platform-tests.yml, pub-crates.yml, release-stable-manual.yml, cross-platform-build-manual.yml, cross-platform-clippy.yml, daily-audit.yml, docs-deploy.yml, codeql.yml | Installer la chaîne d’outils Rust |
Swatinem/rust-cache (v2.9.2) | ci.yml (chemin composite hébergé par GitHub et récupération directe Blacksmith Windows), platform-tests.yml, pub-crates.yml, release-stable-manual.yml, cross-platform-build-manual.yml, cross-platform-clippy.yml, docs-deploy.yml | Mise en cache de build/dépendances Cargo sur les runners hébergés par GitHub et récupération Windows de Blacksmith |
useblacksmith/rust-cache (v3.0.1) | ci.yml (chemin Blacksmith de ./.github/actions/rust-cache) | Mise en cache du build et des dépendances Cargo sur le sticky disk de Blacksmith ; sélectionné par l’entrée use-blacksmith: 'true' que chaque job de compilation Blacksmith transmet au composite (une constante versionnée dans le dépôt) |
docker/setup-buildx-action (v3.11.1, v4.0.0) | release-stable-manual.yml, docker-publish.yml | Configuration de Docker Buildx |
docker/login-action (v3.4.0, v4.1.0) | release-stable-manual.yml, docker-publish.yml, trivy-scheduled.yml | Authentification GHCR |
docker/build-push-action (v6.18.0, v7.1.0) | release-stable-manual.yml, docker-publish.yml | Construction et envoi d’images multi-plateformes |
sigstore/cosign-installer (v3.8.1) | release-stable-manual.yml, docker-publish.yml | Installer cosign pour la signature sans clé des images de conteneur GHCR |
anchore/sbom-action (v0.24.0) | release-stable-manual.yml | Générer des SBOM SPDX et CycloneDX pour chaque version |
aquasecurity/trivy-action (v0.36.0) | docker-image-pr.yml, docker-publish.yml, trivy-scheduled.yml | Analyse des vulnérabilités des conteneurs en mode rapport uniquement |
github/codeql-action/upload-sarif (v3.36.2) | docker-publish.yml, trivy-scheduled.yml, ci-code-analysis.yml | Téléverser les rapports SARIF de Trivy et Semgrep dans l’onglet Security |
github/codeql-action/init (v4.37.8) | codeql.yml | Initialiser l’analyse CodeQL (Rust et JS/TS) |
github/codeql-action/analyze (v4.37.8) | codeql.yml | Téléverser CodeQL SARIF vers l’onglet Sécurité |
La GitHub Release elle-même est créée avec gh release create dans le job publish, et non avec une action de release.
Modèles de liste blanche équivalents (intentionnellement restreints) :
actions/*
dtolnay/rust-toolchain@*
Swatinem/rust-cache@*
useblacksmith/rust-cache@*
docker/*
sigstore/cosign-installer@*
anchore/sbom-action@*
aquasecurity/trivy-action@*
github/codeql-action/upload-sarif@*
github/codeql-action/init@*
github/codeql-action/analyze@*
Exporter la politique effective actuelle :
sh
gh api repos/zeroclaw-labs/zeroclaw/actions/permissions
gh api repos/zeroclaw-labs/zeroclaw/actions/permissions/selected-actions
Toute PR qui ajoute ou modifie une source d’action uses: doit inclure une note d’impact sur la liste d’autorisation dans son corps. Évitez les exceptions larges avec des jokers ; n’élargissez la liste d’autorisation que pour les actions manquantes vérifiées.
Règles de maintenance
- Gardez le
CI Required Gatedéterministe et petit. L’ajout de jobs au gate nécessite un argument de qualité clair. - Toutes les références d’actions tierces doivent être épinglées à un SHA de commit complet (conformément à la politique de liste d’autorisation ci-dessus).
- Maintenez
ci.yml,dev/ci.shet.githooks/pre-pushalignés. Les contrôles partagés doivent résider dansscripts/ci/; chaque appelant invoque l’assistant au lieu de copier ses commandes. Pour le contrôle autonome du protocole du firmware, le point d’entrée local documenté est./dev/ci.sh firmware-protocol. - Maintenez
scripts/ci/prepare_docker_context.sh,docker-image-pr.ymlet le job Docker dansrelease-stable-manual.ymlalignés afin que la validation des PR exerce la même forme de contexte que celle publiée par le workflow de release. - Exécutez
python3 scripts/ci/release_attestation_contract_test.pyaprès avoir modifié l’attestation de publication, la somme de contrôle, le SBOM ou la séquence d’archivage de vérification. - Le job de gate
docs-styleexécutebash scripts/ci/docs_quality_gate.sh(lint markdown + vérification de prose des tirets cadratins) etbash scripts/ci/docs_links_gate.sh(gate de liens sur les lignes modifiées). Exécutez les deux scripts localement avant de pousser des modifications de documentation.
Retour arrière d’urgence
Si la liste d’autorisation bloque une action critique en cours d’incident :
- Rétablir temporairement la politique des Actions sur
all. - Restaurez la liste autorisée
selectedaprès avoir identifié l’entrée manquante. - Enregistrez l’incident et le delta final de la liste d’autorisation.
C’est le seul chemin justifié vers le mode all, et il ne devrait jamais perdurer au-delà de l’incident.