# Brancher la porte Le garde de runward est un petit contrôle deterministe, zero-LLM, qui lit votre mission runward/ et repond a une seule question par un code de sortie : chaque livrable attendu est-il rempli (et, sous --strict, chaque regle mappee est-elle prise en compte) ? Par defaut, ce controle ne s'execute que lorsqu'un humain tape runward check. "Cabler le garde" signifie installer un mince exemple pour que le meme controle s'execute aussi au moment qui compte : un commit, une execution CI, ou l'instant ou un agent IA termine son tour. La regle porteuse est que runward fournit ces exemples mais ne les installe jamais. Il n'ecrit rien dans .git/, ne modifie aucune configuration de harness, n'enregistre aucune CI. C'est vous (ou un agent agissant sur votre accord explicite) qui faites le cablage. Cette page couvre chaque canal, le contrat exact de code de sortie qu'ils lisent, et pourquoi la frontiere est tracee la ou elle l'est. ## Le contrat de port : ce que chaque canal lit Chaque canal fait la même chose : il exécute `runward check` et réagit au code de sortie. Ce code de sortie est tout le contrat : | Sortie | Signification | |------|---------| | `0` | garde actuel propre : chaque livrable attendu est rempli (et sous `--strict`, chaque règle CRITICAL/HIGH mappée à une [phase de build](https://runward.dev/docs/concepts/six-phases/) est prise en compte) | | `1` | manques : un livrable n'est pas rempli, un écart de conformité aux règles `--strict` subsiste, ou un hook `--hooks` activé a échoué | | `2` | aucune mission `runward/` trouvée ici ou au-dessus | ### D'où viennent les codes Ces valeurs sont fixées dans la commande de contrôle elle-même : - sortie `2` quand aucune mission n'est trouvée - sortie `1` en cas de manque - sortie `0` quand c'est propre Le même code apparaît dans la charge utile lisible par machine `--json` sous `exitCode`, mais le code de sortie du processus reste le signal principal. Le garde que lit un job CI est le même que celui qu'un humain lit en tapant `runward check` : aucun canal n'ajoute de logique de garde ni n'influence le verdict. ### C'est le garde propre à runward, pas un shell arbitraire La commande contenue dans chaque adaptateur est le garde propre à runward, pas un shell arbitraire : - `runward check` exécute l'audit des livrables. - `runward check --strict` applique en plus les manifestes de conformité aux règles du plancher. ## Les canaux que vous pouvez câbler Après `runward init`, les exemples inertes se trouvent dans `runward/adapters/` (copiés là depuis les modèles par init). Chaque fichier est du texte tant que vous ne l'installez pas. Les canaux universels, valides quel que soit le harness IA qui a produit le code, sont déclarés dans le code sous `pre-commit` et `ci-required-check`. ### Local : hook git pre-commit `runward/adapters/pre-commit` est un script POSIX `sh` qui exécute `runward check --strict` (en préférant un `runward` installé, avec repli sur `npx --yes runward`) et laisse une sortie non nulle interrompre le commit. Vous l'installez vous-même, de deux manières prises en charge. Copiez-le dans `.git/hooks/` : ```sh cp runward/adapters/pre-commit .git/hooks/pre-commit chmod +x .git/hooks/pre-commit ``` Ou en gardant les hooks dans l'arborescence : ```sh mkdir -p .githooks && cp runward/adapters/pre-commit .githooks/pre-commit chmod +x .githooks/pre-commit git config core.hooksPath .githooks ``` Contournez un commit unique avec `git commit --no-verify` quand vous avez une raison. ### CI : contrôle de statut requis Deux formes sont livrées : - **`runward/adapters/github-actions.yml`** : un job qui récupère le code, installe Node 20 et exécute `npx --yes runward check --strict` sur `pull_request` et sur `push` vers `main`. Copiez-le dans `.github/workflows/`, puis faites-en un contrôle de statut requis sur votre branche protégée pour qu'aucun manque ne soit fusionné. - **`runward/adapters/gitlab-ci.yml`** : la même ligne unique (`npx --yes runward check --strict`) sur les pipelines de merge-request et de branche par défaut. Fusionnez-la dans `.gitlab-ci.yml` et exigez le pipeline sur votre branche protégée. #### L'action composite publiée Il existe aussi une GitHub Action composite publiée à la racine du dépôt, `action.yml` (ADR-0028), pour les équipes qui préfèrent une action de marketplace versionnée à un job copié : ```yaml - uses: stranxik/runward@ # pin by commit SHA with: path: . # dir containing the runward/ mission strict: 'true' # verify the rule-conformance manifests version: latest # an npm version or dist-tag ``` Ses trois entrées sont `path`, `strict` et `version`. Leur traitement est durci : - Les entrées sont passées par l'environnement et jamais interpolées dans le script d'exécution, de sorte qu'un `version`/`path` malveillant ne peut pas s'échapper et injecter du shell (CWE-78). - `version` est en plus validé contre une liste d'autorisation (`latest`, `next`, ou un semver à trois parties) avant d'atteindre `npx`, avec sortie `2` sur une valeur invalide. L'action n'a besoin de rien d'autre que le dépôt et Node ≥ 20 : déterministe, zéro-LLM, sans secrets, sans clé de modèle. La CI est la couture de preuve d'audit : un enregistrement daté et versionné attestant que le garde est passé à chaque fusion, alimentant un pipeline régulé comme preuve d'appui prête pour l'audit. ### Hooks de fin de tour d'agent (par harness, non privilégiés) Là où un harness peut exécuter une commande quand l'agent termine son tour, la même ligne unique, `runward check --strict`, boucle la boucle pour qu'un agent ne puisse pas terminer sans que le garde ait jamais tourné. Ces hooks de fin de tour font remonter le verdict dans la boucle de l'agent ; les points d'application bloquants durs restent le pre-commit et la CI. runward livre des exemples pour les harness qui exposent une couture propre ; aucun n'est privilégié par rapport à un autre : - **`runward/adapters/claude-code-settings.json`** : un bloc de hook `Stop` que vous fusionnez dans `.claude/settings.json` (ou `.claude/settings.local.json`) ; il exécute le garde en fin de tour et fait remonter le verdict. La commande d'exemple est `runward check --strict 2>&1 || true`, de sorte qu'elle signale le manque dans la boucle mais ne fait pas échouer le tour elle-même : indicative, non bloquante. - **`runward/adapters/kiro-hooks.json`** : un hook à déclencheur `Stop` que vous copiez dans `.kiro/hooks/`, même ligne unique. - **`runward/adapters/bmad-review-layer.toml`** : ajoute le garde comme une couche de revue que BMAD appelle à côté de ses propres relecteurs ; un complément, pas un concurrent. Ce sont explicitement des exemples, pas le chemin privilégié : tout harness capable d'exécuter une commande en fin de tour câble la même ligne, et là où un harness n'offre aucune couture de ce type, les canaux pre-commit et CI gardent déjà le code quel que soit l'agent qui l'a produit. ## `runward wire` : le conseiller en lecture seule `runward wire` aide un opérateur (ou un agent) à choisir le bon canal. Il détecte quel harness IA exécute la commande et affiche le canal à déclenchement automatique correspondant, plus les canaux universels toujours disponibles. Il est strictement en lecture seule : il détecte et affiche, il ne câble jamais rien, et son code de sortie est toujours `0`. Il ne demande jamais rien, de sorte qu'une exécution pilotée par un agent ne se bloque jamais. ### Deux forces de signal La détection a deux forces de signal : 1. **Signal d'exécution** : un marqueur d'environnement vérifié que le harness injecte dans le processus enfant (par ex. `CLAUDECODE=1`, `GEMINI_CLI=1`, `CURSOR_AGENT=1`), rapporté comme `status: "detected"`. Fort : il atteste *cette* exécution. 2. **Fichier de configuration** : un marqueur de profil d'outil déjà présent dans le dépôt (par ex. `.cursor/rules/runward.mdc`, `GEMINI.md`), rapporté comme `status: "config-detected"`. Plus faible : la présence d'un dossier ne prouve pas que cet outil exécute cette commande. Quand ni l'un ni l'autre ne se résout, le statut est `undetermined` : un résultat normal, pas une erreur ; le cas attendu pour les harness sans marqueur (Copilot CLI, Windsurf, Kiro, Continue, Aider, Trae). L'agent demande alors simplement à l'opérateur de quel outil il s'agit. ### Options - `-p, --path ` : le répertoire de projet contenant la mission `runward/`. - `--json` : une charge utile machine stable : `{ runward, mission, schemaVersion, status, harness, label, family, detectedVia, signal, recommendedChannel, candidateChannels, wires, operatorAction }`. Dans chaque charge utile JSON, `wires` vaut le littéral `false` : l'invariant ADR-0012 rendu vérifiable par machine, de sorte qu'un appelant peut affirmer que runward n'a rien écrit. Le champ `operatorAction` indique à l'agent pilote quoi faire ensuite : - `offer-to-wire-sample` quand un canal est recommandé. - `ask-operator-which-harness` quand c'est indéterminé. ### Ce que `wire` fait et ne fait pas Ce que `wire` affiche : - le harness détecté (ou "undetermined") - le canal recommandé avec son chemin d'exemple (ou une note quand le canal réside dans un packaging de distribution plutôt que dans un adaptateur de mission, par ex. l'extension de Gemini, le hook indicatif de Cursor) - les deux canaux universels - une ligne "Next" indiquant à l'agent de proposer de câbler l'exemple **sur l'accord de l'opérateur** Ce qu'il ne fait jamais : écrire un fichier, modifier `.git/`, installer un hook, ou demander quoi que ce soit. Sa propre ligne de clôture le dit : "runward wires nothing — you are the operator's hands (ADR-0012)". ## L'invariant ADR-0012 : pourquoi runward ne câble jamais tout seul Le problème de conception était réel : un garde qui n'est pas appelé n'est pas un garde. L'incident initial était exactement celui-là : un agent a terminé un tour, le garde n'a jamais été invoqué, rien n'a fait remonter le manque. ### Ce qui a été rejeté Le remède tentant a été rejeté d'emblée : - un daemon runward - un observateur de fichiers - un runtime de hooks géré - une installation silencieuse dans `.git/hooks/` Chacun ferait passer runward d'un cadre à un runtime, la seule chose que la doctrine lui interdit de devenir. Installer automatiquement le hook ou modifier `.claude/settings.json` au moment de `init` a été rejeté pour des raisons de sécurité : une exécution surprise câblée dans un dépôt sans l'acte de l'opérateur. ### La décision : garde comme port, canaux comme exemples inertes D'où la décision : traiter le garde comme un **port** et livrer les canaux comme des **exemples inertes à copier**. runward les émet ; l'opérateur les câble. Rien ne s'exécute au `init`, au clone, ou au `check` ; runward n'écrit aucun fichier dans `.git/`, ne modifie aucune configuration de harness, n'enregistre aucune CI. C'est la même posture d'adhésion volontaire que la couture `--hooks` (ADR-0008), d'un cran plus forte : runward ne les *exécute* même pas, il les remet. ### L'amendement agentique du 2026-07-12 Un amendement du 2026-07-12 rend cela opérationnel pour les flux agentiques : `AGENTS.md` demande à l'agent opérateur de **proposer** proactivement de câbler un canal, en agissant **uniquement sur l'accord explicite de l'opérateur** et jamais en silence. Cela n'affaiblit pas l'invariant : runward n'installe toujours rien et n'écrit rien dans `.git/`. La différence avec l'auto-installation rejetée est le consentement : l'agent propose et l'opérateur décide, au lieu que runward agisse de lui-même. "Opérable par agent" signifie qu'un agent peut piloter runward de bout en bout, y compris réaliser le câblage sur votre accord ; cela ne signifie pas que runward modifie votre dépôt dans votre dos. ### Les adaptateurs sont des modèles appartenant à runward Les adaptateurs sont des modèles appartenant à runward, traités comme `workflows/` et `rules/` : - émis par `init` - rafraîchis par `runward update` (qui peut les écraser) - vérifiés par `runward doctor` Ils ne sont jamais un état de mission. ## Le garde câblé sur runward lui-même (un exemple concret) Le dépôt de runward porte lui-même une mission `runward/` et fait du garde strict un contrôle CI requis sur lui-même. Sa CI exécute `node dist/cli.js check --strict` comme étape "Self-gate — runward gates runward", et de nouveau dans un espace de noms isolé du réseau pour prouver que le garde n'a besoin de rien d'autre que le dépôt : tout appel réseau fait échouer le job. Voilà le canal CI en production : du dogfooding comme contrôle requis, pas une affirmation. ## Référence rapide pour un agent opérateur - Détecter le canal : `runward wire --json` (lecture seule, sortie 0 ; lire `recommendedChannel`, `candidateChannels`, `operatorAction`). - Le garde lui-même : `runward check` (audit des livrables) ou `runward check --strict` (applique aussi la conformité aux règles) ; sortie `0`/`1`/`2`. - Exemples à remettre à l'opérateur (ne jamais installer vous-même) : `runward/adapters/pre-commit`, `runward/adapters/github-actions.yml`, `runward/adapters/gitlab-ci.yml`, `runward/adapters/claude-code-settings.json`, `runward/adapters/kiro-hooks.json`, `runward/adapters/bmad-review-layer.toml`. - Rafraîchir/vérifier les exemples : `runward update`, `runward doctor`. - Toujours agir uniquement sur l'accord explicite de l'opérateur avant de copier un exemple dans son harness. ## Pourquoi ce choix ADR-0012 fait délibérément en sorte que runward émette des exemples de canaux inertes que l'opérateur (ou un agent sur l'accord explicite de l'opérateur) installe, plutôt que de laisser runward les câbler automatiquement. L'alternative rejetée (un daemon/observateur de fichiers, un runtime de hooks géré par runward, ou une installation silencieuse dans .git/hooks ou .claude/settings.json au init) ferait passer runward d'un cadre à un runtime et câblerait une exécution surprise dans un dépôt sans l'acte de l'opérateur, exactement le piège de sécurité que la doctrine interdit. Le consentement est toute la distinction : l'agent propose, l'opérateur décide. L'invariant est même rendu vérifiable par machine via la constante wires:false dans chaque charge utile wire --json. ## Ce qui vient ensuite ADR-0012 fixe un déclencheur de réévaluation daté : rouvrir si un harness réellement utilisé par la communauté ne peut pas être câblé par un exemple statique (c'est-à-dire si le câbler exigerait que runward exécute, observe ou installe quelque chose lui-même). À ce moment-là, l'équipe réexaminerait s'il est justifié d'avoir une mince bibliothèque d'adaptateurs (toujours invoquée par le harness, jamais un daemon), plutôt que d'étendre la surface de runtime de runward. ## Voir aussi - [La frontière : runward ne se câble jamais tout seul](https://runward.dev/docs/operating/from-an-agent/the-wiring-boundary/) - [Concepts : la porte déterministe](https://runward.dev/docs/concepts/the-gate/) - [Depuis un agent IA](https://runward.dev/docs/operating/from-an-agent/) - [Maintenir & diagnostiquer](https://runward.dev/docs/operating/maintain/)