runward

RW™ · V0.22.0

Docs · Comprendre · Concepts : la porte

Concepts : la porte déterministe.

Le rôle de runward est de vous laisser franchir une porte de phase sur la preuve, pas sur l'affirmation. L'agent de votre harness écrit le code et remplit les livrables ; runward lit ce qui a été écrit et rend un verdict. Cette page explique ce que ce verdict vérifie, ce qu'il refuse délibérément de vérifier, les codes de sortie exacts, pourquoi runward check --strict est l'unique autorité, et pourquoi le relancer vous donne la même réponse à chaque fois. La porte est déterministe et zero-LLM : aucun modèle ne se trouve dans le chemin de décision et rien n'atteint le réseau. (Le LLM vit dans votre harness, là où le code est écrit ; il est absent de la porte.) La porte lit les fichiers comme des octets et les compare ; elle n'exécute jamais votre code et ne demande jamais son avis à un modèle.

runward encadre ce que votre agent IA construit : l'agent écrit le code, une porte déterministe le vérifie sur preuve, et une fois au vert la preuve est scellée.

Le flux runward : l’agent produit, la porte vérifie, la preuve est scelléeVotre agent IA écrit le code ; la porte déterministe et sans LLM le vérifie sur des preuves typées ; une fois au vert, la preuve est gelée par une empreinte SHA-256. Agent IAécrit le codePorte runwarddéterministe · zéro-LLMPreuve scelléegelée · SHA-256 produitvérifie
Le flux runward : l’agent produit, la porte vérifie, la preuve est scellée

Les deux commandes

  • runward check : l'analyse d'écart. Pour chaque livrable attendu à travers les phases de construction de la mission, est-il rempli, commencé (des placeholders subsistent), encore un modèle brut, ou absent. Sortie 1 si quoi que ce soit n'est pas rempli. C'est le contrôle de présence.
  • runward check --strict : la porte de conformité. Tout ce que fait check, plus la vérification, pour chaque phase gardée, du manifeste « Rule conformance » et de la preuve qui le sous-tend. Le drapeau est documenté ainsi : « also verify the rule-conformance manifests: rows, typed pointers, signatures, drift, seal (deterministic) ».

runward status est l'instantané en lecture seule (« où en suis-je ») : porte courante, avancement des phases, journal de décisions, activité, workflows. Il ne change rien.

Ce que --strict vérifie

La porte stricte est délibérément étroite. Pour chaque règle de métier CRITICAL/HIGH rattachée à une phase gardée, elle vérifie que la règle est prise en compte dans le tableau markdown ## Rule conformance de ce livrable.

Les trois manières dont une règle est prise en compte

Une règle est prise en compte de trois manières :

  • applied : avec un pointeur de preuve dans la colonne Evidence. Une ligne applied sans pointeur échoue.
  • deviated : avec un ADR correspondant qui existe réellement dans runward/adr/. Une ligne deviated dont l'ADR est absent échoue.
  • n/a : avec une vraie raison d'une ligne. Une raison de moins de 8 caractères, ou un jeton de modèle entre crochets comme [why], est considérée comme triviale et échoue.

Tout statut hors de applied | deviated | n/a échoue, et un statut vide (« a scaffolded row is not a decision ») échoue avec son propre message.

Les couches de vérification en plus de la bonne forme du manifeste

En plus de la bonne forme du manifeste, --strict exécute ces couches, dont les violations rejoignent toutes le même verdict :

  • Plancher de non-vacuité : le mapping ne peut pas être discrètement vidé pour faire passer la porte sur un ensemble vide. Chaque phase a un minimum figé de règles CRITICAL/HIGH rattachées (architect: 6, topology: 4, floor: 10, govern: 12, handover: 4) ; descendre en dessous lève une violation (mapping).
  • Lignes inconnues / dupliquées : une règle du manifeste absente de runward/rules/ échoue (avec un indice de renommage/suppression quand il est connu), et une règle listée deux fois échoue.
  • Résolution de preuve (pointeurs typés) : les pointeurs de la forme file:PATH[:LINE][#SYMBOL], test:PATH[::NAME], adr:NNNN doivent se résoudre vers des fichiers réels, non vides, réguliers, à l'intérieur du projet (un chemin absolu ou un ../ qui échappe à toute base est rejeté, non résolu), le fichier doit avoir au moins :LINE lignes, et un #SYMBOL / nom de test nommé doit réellement apparaître dans le contenu.
  • Signatures : une règle peut déclarer une regex signature: ; le contenu pointé par la preuve applied doit y correspondre, ce qui permet d'attraper de façon déterministe le « cité mais pas appliqué ». Les regex dangereuses à quantificateurs imbriqués sont écartées en amont pour éviter un blocage de CI auto-infligé.
  • Drift : un pointeur en prose applied dont le chemin de fichier ne se résout plus échoue à la porte (existence seule ; les pointeurs typés sont diagnostiqués par la couche de preuve à la place, de sorte qu'une ligne n'est jamais diagnostiquée deux fois).
  • Scellé de preuve (--freeze, optionnel) : hache chaque fichier de preuve résoluble dans runward/evidence-lock.json (SHA-256). Lors des exécutions --strict ultérieures, un fichier scellé qui a changé ou disparu échoue jusqu'à ce que vous re-vérifiiez et re-scelliez ; les chemins de lock falsifiés qui échappent au projet sont rejetés.
  • Cycle de vie de reconstruction : un ADR reconstruit rétroactivement est une hypothèse jusqu'à ratification. Les ADR encore marqués DRAFT-, Status: hypothesis, ou why: UNKNOWN échouent à la porte (« an agent's guess must not pass as a decision »).

Les cinq paires gardées

Les cinq paires gardées (phase → livrable) sont :

  • architect → architecture.md
  • topology → execution-topology.md
  • floor → floor.md
  • govern → governance/threat-model.md
  • handover → handover.md

Ce qu'elle ne fait explicitement PAS

C'est la frontière porteuse. La porte prouve qu'une décision a été tracée et pointe vers quelque chose de réel, jamais que le code est bon.

  • Elle ne juge pas la qualité du code ni si le pointeur implémente réellement la règle. Ce jugement « stays the operator's, at the gate » (reste celui de l'opérateur, à la porte).
  • Elle n'exécute jamais votre code, vos tests ou votre build. La couche de preuve « reads files; it never executes anything and never calls a model » (lit des fichiers ; elle n'exécute jamais rien et n'appelle jamais un modèle). La porte « n'est pas un runtime ».
  • Elle n'appelle jamais un modèle ni le réseau. Zero-LLM, zero-network par construction.
  • La preuve comportementale et la passe de vérification sémantique sont consultatives, au-dessus de la porte. Quand une porte stricte est verte, runward signale si la commande de votre suite de tests et les constats du workflow de vérification sont présents et frais, mais « advisory, agent-executed, never blocks the gate » (consultatif, exécuté par l'agent, ne bloque jamais la porte).
  • La couverture documentation/décision (--coverage) est consultative et « does not affect the verdict » (n'affecte pas le verdict).

Deux preuves, tenues séparées à dessein : cette porte est la preuve documentaire (les décisions sont tracées) ; la preuve comportementale est votre suite de tests, que runward pointe mais n'exécute jamais.

Codes de sortie (exacts)

D'après le code, check utilise trois codes de sortie :

  • 0 : porte courante propre. Aucun livrable non rempli, aucun écart strict, aucun hook en échec (clean = gaps === 0 && strictGaps === 0 && hookFailed === 0). Le processus sort 0 par défaut.
  • 1 : écarts. Au moins un livrable non rempli, ou (sous --strict) au moins une violation de conformité/preuve/scellé/reconstruction, ou un hook optionnel en échec. Défini via process.exitCode = 1. Le récapitulatif nomme les décomptes : « N deliverable(s) not filled · N floor rule-conformance gap(s) · N hook(s) failed ».
  • 2 : aucune mission trouvée. Pas de runward/ ici ni au-dessus ; la commande vous dit de lancer runward init d'abord (process.exit(2)). status utilise le même code 2 quand aucune mission n'est trouvée.

Cela correspond au contrat valable pour toute la CLI : 0 = success · 1 = gaps/warnings · 2 = missing prerequisite or CLI misuse. En mode --json, le même verdict est émis comme un objet stable avec un champ exitCode (0 propre / 1 écarts / 2 sans mission) et une chaîne verdict, mais le code de sortie reste le signal principal.

Pour un agent qui pilote runward : gardez sur le code de sortie (ou le exitCode du JSON) ; ne grattez pas le texte humain.

Ce que signifie « l'autorité, c'est check --strict »

runward livre des skills et des workflows qui aident un agent à appliquer la bonne règle au point d'action, et un workflow de vérification sémantique qui recontrôle de façon adverse le cité-contre-appliqué. Aucun d'eux n'est le juge.

check --strict est « the sole authority and verifies the manifest deterministically. A rule surfaced here but not accounted for still fails the gate » (l'unique autorité et vérifie le manifeste de façon déterministe ; une règle remontée ici mais non prise en compte échoue toujours à la porte).

La décision de conception est explicite : « The gate stays the sole authority. A skill only helps the agent apply a rule; it never replaces the manifest verification. check --strict remains the deterministic judge... A skill without the gate is just advice » (ADR-0018, point de décision 2).

L'alternative, un cadrage souple jugé par LLM où charger un skill vaut conformité, est exactement ce que runward refuse, parce qu'un juge probabiliste peut être convaincu de changer son verdict.

En pratique

La seule chose qui permet de fermer une phase est check --strict renvoyant 0. Les sorties consultatives (preuve comportementale, constats de vérification, couverture) informent l'humain qui franchit la porte ; elles ne la gardent jamais.

Déterminisme (relancer → même verdict)

Le verdict est une fonction pure des octets sur le disque. Dans le chemin de décision, la porte :

  • lit les fichiers de règles, les manifestes et la preuve pointée ;
  • trie ses entrées de façon déterministe (par ex. expectedRules(...).sort(), et le lock de preuve écrit les clés en ordre trié) ;
  • compile les signatures vers un RegExp fixe ;
  • compare : pas d'horloge, pas d'aléa, pas de modèle, pas de réseau dans le chemin de décision.

Comme rien n'est exécuté et rien n'est échantillonné, l'exécuter deux fois sur le même arbre donne un verdict identique : le README l'énonce ainsi, « rerun it and get the same verdict » et « reruns byte-for-byte ».

C'est aussi pourquoi elle « cannot be jailbroken by injection » (ne peut pas être jailbreakée par injection) : il n'y a aucun modèle dans le chemin de la porte à jailbreaker.

Une note sur les écritures (la porte est surtout en lecture, et ne touche jamais .git/)

check seul et check --strict ne font que lire. Deux comportements écrivent, tous deux dans le répertoire de mission et tous deux optionnels, jamais dans le .git/ de votre dépôt :

  • --freeze écrit runward/evidence-lock.json (et refuse de sceller une porte rouge).
  • --hooks exécute des commandes rédigées par l'opérateur, configurées dans runward/hooks.json.

runward n'installe pas de hooks git et ne câble pas la porte dans votre CI à votre place ; les adaptateurs sont des exemples inertes que vous câblez de votre propre initiative. Un agent peut piloter runward de bout en bout, y compris réaliser ce câblage quand vous le lui dites, mais runward lui-même ne modifie jamais votre dépôt en silence.

Aide-mémoire pour un agent en opération

  • Franchir une porte : runward check --strict → exiger la sortie 0.
  • Lisible par machine : runward check --strict --json → analyser .verdict (clean/gaps) et .exitCode ; le tableau conformance liste {scope, rule, problem} pour chaque écart.
  • Sceller une porte verte pour détecter l'érosion ultérieure : runward check --freeze (implique --strict, écrit runward/evidence-lock.json).
  • Ne jamais présenter un skill chargé, un workflow de vérification qui passe, ou un artefact de test frais comme « la porte est passée » : seule la sortie 0 de check --strict l'est.

Pourquoi ce choix

Le choix de conception central est de garder la porte déterministe et sans LLM plutôt que de laisser un modèle juger si le code satisfait une règle.

L'alternative rejetée est le cadrage « souple » qu'utilise tout concurrent de la livraison : un LLM lit le code et décide s'il est conforme, ou charger un skill vaut conformité. runward le rejette parce qu'un juge probabiliste peut être jailbreaké par injection de prompt et ne se relance pas à l'identique, de sorte que son verdict n'est pas quelque chose sur quoi un auditeur ou un pipeline de CI puisse s'appuyer (ADR-0018 section 2).

Le coût de ce choix est l'honnêteté sur le périmètre : la porte ne peut prouver qu'une décision a été tracée et pointe vers du contenu réel, pas que le code est correct. runward garde donc la preuve comportementale (vos tests) et la passe de vérification sémantique explicitement consultatives et au-dessus de la porte.

Les planchers dans constants.ts et le contrôle de non-vacuité existent pour fermer la faille évidente d'une porte déterministe : vider le mapping de règles pour que la porte passe sur rien.

Le dessin lui-même est plus vieux que l'IA. Chaque fois que le logiciel a absorbé un composant instable, l'industrie a convergé vers la même réponse : un contrat stable devant, l'instable derrière. L'information hiding de Parnas en 1972, puis les ports et adaptateurs de Cockburn en 2005, redits en onion et en Clean Architecture. runward applique cette leçon vieille de cinquante ans au composant le plus instable que le logiciel ait jamais absorbé : un modèle non déterministe par conception. Le modèle propose derrière un port ; le code déterministe décide. La même lignée façonne le défaut d'architecture de la méthode, un cœur modulaire derrière des ports et adaptateurs, et il reste un défaut : la porte vérifie que la décision a été confrontée et tracée, jamais le style.

← Docs