Chapitres

Sur cette page

DOC-04 / Référence technique · Chapitre 05

Automates, crons et runs : couche d'exécution non-conversationnelle

Cette page décrit l'architecture d'exécution déterministe de Synedre OS : façades Python, enrobage cron, registre des automates et frontière entre runs et tâches agents.

Automates, crons & runs

Cette page décrit la couche d'exécution non-conversationnelle du harness Synedre OS : les façades Python, leur enrobage cron, le registre des automates, le double système d'ordonnancement (planificateur applicatif + filet crontab Linux) et la frontière entre la doctrine run et l'unité d'exécution agent. Public visé : ingénieur reprenant le harness.

Note de sécurité : la table crontab réelle contient des secrets en clair (mots de passe base de données, jetons porteurs). Ils ne sont pas reproduits ici. Cette dette de hardening — secrets inline dans crontab — est signalée en fin de page.

1. Modèle mental : agent (pense) vs automate (exécute)

Le harness sépare deux registres :

Agent Automate
Rôle Pense, décide, délègue (ReAct) Exécute une routine déterministe
Support Persona LLM (Claude / Mistral) Script Python du répertoire de façades
Table Table des agents et personas Registre des automates
Déclenchement Spawn Atlas, outil Task, unité d'exécution agent Cron, CLI, planificateur applicatif
Lien entre les deux Table de liaison automate → agent propriétaire ; un agent peut engendrer un automate ou une unité d'exécution agent.

Un automate ne « réfléchit » pas : il peut appeler un LLM (génération de contenu, classification) mais son flux de contrôle est codé en dur. La pensée vit dans les personas et dans Atlas ; l'exécution répétable vit dans les automates.

   DÉCLENCHEUR (email, cron, planificateur)
          │
          ▼
   ┌──────────────┐  délègue   ┌──────────────┐  spawn   ┌──────────────┐
   │  Atlas (run) │ ─────────► │  unité agent │ ───────► │ LLM CLI /    │
   │  registre    │            │  (exécution) │          │ façade Python │
   └──────────────┘            └──────────────┘          └──────────────┘
          ▲                                                     │
          │                          cron / planificateur ──────┘
          │                          (automate planifié)
   cadran des runs                registre automates + journaux d'exécution

2. Les façades Python

Le répertoire de façades contient plusieurs centaines de fichiers .py (plus de 340 au moment de cette rédaction, en croissance continue — le chiffre de 209 cité dans une relecture précédente de juin 2026 est périmé). Ce ne sont pas tous des automates planifiés : la majorité sont des façades (point d'entrée unique pour une capacité), certaines étant des bibliothèques partagées, des outils manuels ou des scripts ponctuels.

Le registre canonique de classification vit en base de données, pas dans les fichiers. Deux axes :

  • kind — nature technique :
    • recurring — planifié (cron ou planificateur applicatif)
    • oneshot — déclenché à la demande
    • tool — outil invoqué par un agent ou un skill
    • lib — bibliothèque partagée, non exécutable seule
    • meta — méta-outillage (enrobage cron ; l'outil d'enregistrement cron historique est archivé, son implémentation n'est plus active)
  • caste — groupe d'agents propriétaire. Onze valeurs distinctes en registre (au 19 juillet 2026) :
    • Vigies (26 façades) — audits, QA
    • Oracles (22) — veille, reporting
    • Scribes (21) — rédaction
    • Bâtisseurs (19) — build, provisionnement
    • Horlogers (17) — infra, session, backups
    • library (16) — bibliothèques
    • Tisserands (15) — SEO, maillage
    • execution (10) — exécution générique
    • veille (3) — surveillance
    • Cellériers (3) — finances, trésorerie, remises
    • phronesis (1) — délibération stratégique

    ⚠️ La casse est incohérente (execution en minuscule vs Vigies avec majuscule) — dette de normalisation à traiter.

Taxonomie fonctionnelle des familles de façades

Famille Rôle Remarques
Atlas — orchestrateur Pipeline inbox → intention → spawn → expédition. Inclut les façades de scrutation boîte de réception, de classification, de spawn, d'expédition, de calibration du classifieur, de dispatch client et de composition de réponse. ⚠️ Les façades de santé et de monitoring Atlas spécifiques ont été supprimées (code mort confirmé, aucun remplaçant dédié) ; la surveillance générique reste assurée par les façades de monitoring SRE décrites ci-dessous.
Audits Détection de drift, findings sécurité, audit de schéma, audit nocturne, audit de sitemap, audit d'articles, reconnaissance de sécurité multi-tenant. ⚠️ Une façade d'audit d'accessibilité a été supprimée mais reste appelée par la façade d'audit de page (flag --a11y/--all) — appel cassé, FileNotFoundError silencieux capturé localement. La détection de cannibalisation blog vit désormais dans la bibliothèque de similarité, consommée par le moteur de publication et le vérificateur factuel.
Backups Dump base de données et fichiers vers stockage objet, test de restauration mensuel. Inclut des scripts shell complémentaires pour les opérations bas niveau.
Brainstorm Jobs asynchrones de génération, promotion, challenge, rétrogradation et narration d'idées ; worker en boucle + filet cron ponctuel.
Blog / SEO Génération et hygiène de contenu, SEO technique, géolocalisation, sitemap. ⚠️ La façade de maillage interne est archivée, plus active.
Email / inbox Façade unique d'envoi (aucun accès SMTP/IMAP direct en dehors), synchronisation de la boîte de réception, recherche, scan de leads, garde-fou email. L'envoi client passe obligatoirement par la façade ; les identifiants de messagerie restent encapsulés.
Navigateur automatisé Automatisation navigateur headful (worker résidentiel), jobs de QA browser, captures d'écran.
Finances / facturation Synchronisation bancaire, import de relevés, facturation récurrente, relances. ⚠️ La façade de relance générique a été absorbée dans le package de facturation mais n'est plus câblée à aucun cron ni CLI. Seule la relance spécifique à un client tourne réellement. L'import d'un outil comptable tiers n'est plus actif ; seuls des champs legacy subsistent en base.
Brand / veille Surveillance de la marque, veille technologique, synchronisation des avis Google. ⚠️ La façade de veille newsletters est archivée, plus active.
Mémoire / apprentissage Indexation vectorielle (pgvector), synchronisation des embeddings, rappel sémantique, métriques de mémoire, détection de patterns, consolidation nocturne, post-mortems (cicatrices).
SRE / garde-fous Surveillance des coûts, détection de processus incontrôlés, alerting, garde-fou en écriture production, notification fondateur.
Doc — boucle de maintenance Détection de drift doc↔code, couverture des angles morts, réparation déterministe des références mortes, régénération profonde autonome, publication vers synedre.com, graphe et statut des organes (chantiers actifs), cadran de maintenance et synchronisation publique. ⚠️ Trois façades ont été supprimées à tort par l'outil d'élagage mais restent appelées — voir §7.1 pour le détail.
Autonomie Seeder de chantiers en mode automatique, cadencé toutes les 2 minutes mais borné à la fenêtre horaire du jour ; amorce le pipeline du worker de tâches. Voir §8.
Bibliothèques / infra Accès base de données, journalisation, chargement d'environnement, routage vers le fournisseur IA, rotation des logs, enrobage cron. Briques partagées, non planifiées directement.

Le garde-fou d'enrobage des tâches planifiées

Tout automate planifié est lancé à travers un composant de garde-fou qui s'intercale entre le planificateur système et le script cible. Ce garde-fou est responsable de cinq grandes fonctions :

  1. Chargement de l'environnement — au démarrage, il lit les fichiers de configuration d'environnement et injecte les variables dans le processus, sans jamais écraser une variable déjà définie.
  2. Disjoncteur automatique — il consulte le journal des erreurs consécutives. Au-delà de dix échecs d'affilée, le script est désactivé automatiquement et le système s'interrompt proprement, sans générer de déluge de logs (ce mécanisme évite plusieurs centaines de lignes parasites par jour).
  3. Exécution en sous-processus — le script cible est lancé en processus enfant avec un répertoire de travail et un chemin Python isolés. Un délai d'expiration de 5 minutes s'applique par défaut ; certains automates à longue durée bénéficient d'un délai étendu allant jusqu'à 30 minutes.
  4. Auto-réparation à chaud — en cas d'erreur, le garde-fou classifie le message d'erreur et tente un correctif automatique avant de relancer le script :
    • import manquant → injection de l'import connu
    • droits insuffisants sur un journal → redirection vers un répertoire autorisé
    • dossier absent → création du dossier dans le périmètre autorisé
    • module Python manquant → installation dans l'environnement virtuel
    • conflit de nom de variable de journalisation → renommage automatique
    • erreur réseau → trois tentatives avec backoff exponentiel (2 s, 5 s, 10 s)
  5. Mode audit tolérant — certains automates sont déclarés en mode « soft-fail » : un code de sortie non nul est interprété comme un résultat d'audit (des constats, pas un crash), le compteur d'erreurs est réinitialisé, et le code de sortie est transmis aux consommateurs en aval.

Chaque erreur est enregistrée dans le journal de persistance des erreurs d'automates, avec les champs suivants : type d'erreur, trace complète, indicateur de correction automatique, détail du correctif appliqué, succès du retry, nombre d'échecs consécutifs et statut de désactivation.

Note sur la protection contre le recouvrement : le garde-fou ne pose pas de verrou système par défaut. Certains automates ajoutent manuellement un verrou exclusif pour leurs modes d'exécution unitaires. La majorité des entrées n'ont pas de verrou explicite — la protection contre les exécutions simultanées repose sur la combinaison délai d'expiration + fréquence de planification.

Le système de runs

La doctrine « run » : une exécution scopée par Atlas

Un run désigne une exécution pilotée par Atlas sur un périmètre défini — le vaisseau-mère ou un tenant client. Ce périmètre est résolu automatiquement à partir de la base de données : le contexte technique (VPS concerné, client, interlocuteur, boîte mail) est chargé sans configuration manuelle.

La table des runs enregistre pour chaque run : un identifiant, la source déclencheuse, le type de déclencheur, le périmètre, un titre, le statut, le type et l'identifiant de la référence associée, ainsi que les horodatages de début et de fin.

Deux déclencheurs sont observés en production :

Déclencheur Source Porte d'entrée
email Boîte de réception Atlas Emails transférés vers Atlas, classifiés par intention (run / question / chantier / bruit)
chat Console Hub Console scopée dans le Hub, avec sélection de périmètre et chat Atlas

Un troisième déclencheur planifié (cron) est documenté en doctrine mais n'a pas encore été observé en base de données.

La tâche d'exécution agent

La tâche d'exécution agent est distincte du run. C'est l'unité de travail déléguée à un agent spécifique. Un run peut engendrer une ou plusieurs tâches d'exécution lorsqu'Atlas délègue à un agent.

Chaque tâche enregistre : un nom de code, l'agent assigné, un titre, le prompt, le template utilisé, le périmètre, les critères de sortie, le statut, le créateur, le journal de sortie, le code de retour, la consommation de tokens, le coût estimé et le modèle employé.

Répartition des statuts observés en production (sur environ 750 tâches) :

  • completed — majorité (~52 %)
  • failed — ~25 %
  • done — ~21 % (statut distinct de completed, à surveiller si les deux divergent sémantiquement)
  • cancelled, pending, running — minorité

Les templates les plus utilisés sont code (très large majorité) et audit, avec une fraction de tâches sans template déclaré.

L'exécuteur de tâches agents

Un automate planifié à la minute récupère les tâches en attente, les marque en cours, lance l'agent selon le template, streame la sortie dans le journal de la tâche, puis marque la tâche completed ou failed. Il opère en deux modes :

  • Mode simulation (défaut) — aucun spawn réel, utilisé pour les tests.
  • Mode production — spawn de l'agent avec des permissions adaptées au template :
    • research → lecture seule, commandes Git et exploration fichiers uniquement
    • audit → idem + écriture du rapport
    • code → permissions étendues, confinées au périmètre déclaré de la tâche

Résolution dynamique du modèle IA

Avant de spawner un agent, l'exécuteur résout dynamiquement le modèle IA à utiliser. Il lit la recommandation de modèle stockée sur la tâche (calculée par l'estimateur de tokens lors de la planification), normalise l'alias (opus, sonnet ou haiku selon le niveau détecté), et passe ce modèle résolu au spawn. En l'absence de recommandation, le modèle par défaut est sonnet.

Attention : jusqu'à une correction récente, la recommandation de modèle était calculée et stockée pour plusieurs milliers de tâches, mais jamais lue lors du spawn réel — toutes les tâches autonomes s'exécutaient en Sonnet, quel que soit le tier recommandé. Ce câblage est désormais actif. Le sous-run de vérification qualité reste volontairement fixé en Sonnet.

La porte de validation live avant clôture

Pour les tâches portées par un agent de rôle infrastructure ou frontend, et dont le chantier déclare une URL de cible, l'exécuteur effectue une vérification navigateur réelle juste avant de marquer la tâche done :

  1. Un agent de navigation (Chromium) visite la cible déclarée.
  2. Si la cible répond en erreur (502, page vide) → le résultat de la validation qualité est rétrogradé en échec ; la tâche n'est pas clôturée, une cicatrice est enregistrée.
  3. Si l'agent navigateur est indisponible → la porte s'ouvre par défaut (fail-open) et la tâche est clôturée, mais l'événement est loggué ; la porte de mise en production en aval reste, elle, fermée en cas de doute (fail-closed).

Cette porte corrige un pattern de faux-positifs où un agent déclarait une tâche terminée (déploiement, certificat) sur la seule base de sa propre déclaration, sans que la cible soit réellement accessible.

Le journal verbeux d'exécution des automates

Un journal dédié enregistre chaque exécution d'automate (environ 124 000 entrées en croissance continue) avec : le nom de l'automate, l'environnement, le résultat, la durée en secondes, le nombre d'étapes, les compteurs d'erreurs et d'avertissements, le détail des étapes, et le contexte d'exécution.

Ce journal est distinct du journal des crashs du garde-fou, qui n'enregistre que les échecs au niveau du wrapper, pas les résultats métier des automates.

La relation entre les différentes tables de persistance s'organise ainsi :

Run Atlas (scopé)
   └─ engendre ─► Tâche d'exécution agent ──journal──► output_log de la tâche

Registre des automates ──exécution planifiée──► Journal verbeux des automates
                                             └─► Journal des crashs wrapper (uniquement)

Ordonnancement : deux schedulers en parallèle

Le scheduler applicatif intégré

Le vaisseau-mère expose un mécanisme de tâches planifiées natif à son framework, activé en mode expérimental. Les tâches de supervision vivent dans une couche dédiée au moteur d'automates, fusionnée au démarrage — distincte du répertoire de tâches généralistes. Le container applicatif doit être reconstruit après toute modification de la configuration du scheduler pour que les changements prennent effet.

Historique : le scheduler applicatif a connu une interruption de plusieurs jours en mai 2026, puis a été restauré dans le cadre d'un chantier de réactivation. Les tâches ci-dessous sont actives depuis lors.

Un garde-fou transverse (guard) court-circuite certaines tâches si une variable d'environnement dédiée n'est pas activée dans le container — ce qui permet de désactiver sélectivement les crons de supervision selon le contexte de déploiement.

Rôle de la tâche Fréquence cron Guard
Traitement de la file d'e-mails sortants Toutes les 2 minutes —
Surveillance de disponibilité (uptime) Toutes les 15 minutes Contexte interne requis
Veille dictionnaire / terminologie Toutes les 30 minutes Contexte interne requis
Veille dépendances logicielles Quotidien à 02 h Contexte interne requis
Réunion quotidienne automatisée Quotidien à 08 h Contexte interne requis
Surveillance des certificats SSL Quotidien à 09 h Contexte interne requis
Veille marque / brand Quotidien à 12 h Contexte interne requis

Tâches intentionnellement désactivées : la synchronisation de boîte de réception et la synchronisation des e-mails clients restent désactivées. Le client IMAP utilisé monopolise la boucle d'événements Node.js et provoque des timeouts en cascade sur le moteur de base de données. La réactivation est conditionnée à l'implémentation d'un client IMAP non bloquant, prévue dans une phase ultérieure du même chantier.

Tâches présentes mais non planifiées : plusieurs modules de supervision existent (hygiène de blog, audit de pages, veille sitemap, vérification factuelle, veille schéma structuré…) mais ne sont rattachés à aucune entrée cron — ils peuvent être invoqués manuellement ou planifiés ultérieurement.

Le filet crontab système

Le crontab Linux du serveur constitue le second scheduler, indépendant du premier. Il représente l'essentiel de la charge planifiée et joue le rôle de filet de sécurité : si le scheduler applicatif est hors ligne, les automatismes critiques continuent de tourner. Au dernier relevé (juillet 2026), le crontab compte environ 120 lignes actives sur un total de près de 300. Les lignes commentées constituent une archéologie de l'évolution du système — traces de vagues de déploiement, de pauses expérimentales et de désactivations documentées.

Les automates planifiés se répartissent en plusieurs familles :

  • Automates supervisés par un enveloppe de fiabilité : la majorité des processus récurrents passent par un wrapper Python qui assure la journalisation des erreurs et le mécanisme d'auto-réparation. Cette famille couvre notamment :
    • la supervision de disponibilité (toutes les 2 min) ;
    • les sauvegardes quotidiennes ;
    • les audits d'automates et de sauvegardes ;
    • la synchronisation bancaire et la facturation récurrente ;
    • la lecture de boîte de réception Atlas (toutes les minutes) et le spawn d'agents (toutes les 5 min) ;
    • la boucle de maintenance documentaire (toutes les 6 h) ;
    • la boucle de maintenance nocturne du système (quotidien à 05 h) ;
    • la boucle d'autonomie (toutes les 2 min, auto-limitée par une fenêtre de disponibilité, cf. §8.1 — le docstring du fichier mentionne encore une fréquence quotidienne périmée) ;
    • la synchronisation des embeddings vectoriels.
  • Automates Python sans enveloppe de fiabilité : plusieurs processus tournent en invocation directe, sans logging centralisé ni auto-réparation automatique. Cette situation constitue une dette technique à résorber. On y trouve :
    • l'indexation du graphe de connaissances (toutes les 15 min) ;
    • le worker de traitement des travaux (toutes les minutes) ;
    • l'indexation des compétences (toutes les 15 min) ;
    • le détecteur de blocages (toutes les 15 min) ;
    • les alertes SRE et coût (respectivement toutes les 30 et 6 min) ;
    • le détecteur de dérives (toutes les 15 min) ;
    • l'extracteur d'événements de négociation (toutes les 2 min, avec verrou exclusif) ;
    • la détection de leads entrants (deux fois par jour) ;
    • le rafraîchissement des KPI de cicatrices, la synchronisation des avis Google, le traitement des revues documentaires externes ;
    • la publication documentaire, les tests de restauration de sauvegardes, la rotation de logs, les métriques mémoire, l'indexation des sessions IA, les moniteurs et détecteurs de propositions de compétences, les détecteurs de patterns, le résumé quotidien.
  • Worker de brainstorming : lancé au démarrage du système en mode boucle persistante (processus détaché). Un filet cron toutes les 2 minutes le relance si le processus est absent ; un second filet toutes les 3 minutes, avec verrou exclusif, rattrape les passes manquées.
  • Scripts d'infrastructure (shell et Node.js) :
    • analyse de la flotte de sites et des dépendances TypeScript (nuit) ;
    • sauvegardes PostgreSQL et fichiers vers stockage objet S3, pour le vaisseau-mère et pour plusieurs VPS clients (horaires décalés de 02 h à 03 h 45), avec notification de défaillance systématique ;
    • test de restauration mensuel depuis S3 ;
    • synchronisation du cerveau vectoriel (toutes les 30 min) ;
    • nettoyage des verrous de chantier (toutes les 5 min) ;
    • chien de garde des verrous Atlas (toutes les 15 min).
  • Appels HTTP directs :
    • drain de la file d'e-mails sortants toutes les minutes via appel interne — double sécurité avec la tâche du scheduler applicatif ;
    • synchronisation de session toutes les 5 minutes vers un endpoint externe.
    ⚠️ Dette P0 : l'appel HTTP de synchronisation de session embarque un jeton d'authentification en clair dans la ligne crontab. Cette situation constitue une fuite de secret à corriger en priorité (voir §7).
  • Consolidation mémoire : script mensuel de consolidation du graphe mémoire, avec verrou long (10 min) pour éviter les exécutions concurrentes.
  • Quarantaine logiciels open source : script de nettoyage annuel à date fixe (mi-juin).

Automates retirés du crontab : le notificateur fondateur et le module de veille sectorielle existent comme façades actives mais n'ont aucune ligne planifiée — ni active ni commentée.

⚠️ Réconciliation registre ↔ ordonnancement (audit à produire) : le registre canonique des automates classe chaque automate par nature (ponctuel, récurrent, réactif…), mais aucun mapping automatisé ne relie aujourd'hui les automates de type récurrent à leur ligne crontab ou à leur tâche dans le scheduler applicatif. Il est donc impossible de savoir combien d'automates sont orphelins — enregistrés comme récurrents mais non planifiés en pratique. Un audit croisé (registre × crontab actif × scheduler applicatif) est à produire.

Le browser-worker : automate distribué hors datacenter

La famille d'automates navigateur est le seul sous-système où l'exécution sort physiquement du datacenter. Deux raisons distinctes de quitter le serveur central engendrent deux topologies d'egress qu'il ne faut pas confondre :

Topologie Composant VPS Où tourne le navigateur Ce qu'il traite Mode
Proxy résidentiel sortant Agent navigateur côté serveur Chromium headless sur le VPS du vaisseau-mère La réputation IP Sans fenêtre + mode furtif
Worker headful distant Worker navigateur déporté Chrome headful sur la machine résidentielle Le fingerprint navigateur Fenêtre visible, IP résidentielle directe

6.1 Pourquoi un Chrome headful résidentiel plutôt qu'un Chromium headless VPS

Un tunnel proxy sortant (décrit dans le runbook dédié) fait sortir le trafic du vaisseau-mère par une IP résidentielle via un tunnel SSH inverse depuis une machine locale. C'est suffisant pour les sites qui ne discriminent que sur la réputation IP datacenter. Un garde-fou d'egress vérifie, avant tout lancement, que le proxy est bien actif et que l'IP de sortie résolue n'est pas celle du serveur central — zéro egress accidentel par le datacenter.

Cependant, Cloudflare Turnstile / managed challenge ne juge pas l'IP : il juge le fingerprint du navigateur (signaux navigator.webdriver, plugins, window.chrome, WebRTC, marqueurs headless). Un Chromium headless via proxy conserve un fingerprint de bot même avec une IP propre. Conséquence doctrinale : classer la protection AVANT de coder le flow. Si le site applique Turnstile ou managed challenge → Chrome headful sur machine résidentielle obligatoire ; le mode furtif headless ne passe pas.

D'où le worker headful : le vrai Chrome (fenêtre visible, canal de distribution stable) tourne sur une machine Windows/Mac résidentielle allumée en permanence, avec une IP résidentielle directe. Pas de proxy, pas de garde-fou d'egress ici : on est déjà sur la bonne IP, donc Turnstile passe naturellement.

6.2 File de jobs navigateur et cycle de vie d'un job

La file de travaux navigateur vit en base de données, avec les colonnes suivantes : identifiant du job, type (kind, varchar 32), charge utile (payload, jsonb, défaut {}), statut (contraint à queued | running | done | failed), résultat (jsonb), message d'erreur (text), identifiant de la machine exécutante (host), nombre de tentatives, horodatages de début/fin, dates de création et de mise à jour. Des index couvrent le statut seul et la paire (statut, id) pour le claim ordonné.

Note : Ce sous-système est jeune et peu sollicité — la file ne contient généralement que quelques lignes actives.

L'inversion de contrôle est la clé architecturale : le VPS du vaisseau-mère n'a aucun accès entrant vers la machine résidentielle (NAT, IP dynamique). C'est le worker distant qui poll le vaisseau-mère via SSH sortant. Cycle complet :

[VPS — vaisseau-mère]                    [Machine résidentielle — Windows/Mac]
  Atlas / skill / automate métier          Worker navigateur --loop (daemon, intervalle 5 s)
   │ enqueue                                 │
   ▼                                         │  (1) git pull --ff-only (idle uniquement) + redémarrage si MAJ
  CLI d'enqueue                             │  (2) SSH --claim --host <id>  ─────────────────┐
   │  INSERT status=queued                   │                                                │
   ▼                                         ▼                                                │
  File de jobs ◄──── FOR UPDATE SKIP LOCKED ── claim atomique status=running ◄───────────────┘
                                             │
                                             │  (3) Dispatch par kind (whitelist stricte)
                                             │       Chrome headful (fenêtre visible)
                                             │       → scraping Malt : run + récupération code 2FA via SSH
                                             │       → checkout e-commerce : mode simulation forcé
                                             ▼
  File de jobs ◄──── SSH --finish --status done|failed (résultat+erreur via STDIN JSON)
       result jsonb / error                  │
                                             │  purge screenshots temporaires en fin de run

Détails des étapes :

  1. Enqueue (VPS) — Le CLI d'enqueue refuse tout kind hors whitelist, valide le JSON payload, puis insère en statut queued avec un dollar-quoting randomisé (protection anti-injection).
  2. Claim atomique (worker → VPS) — Le worker prend le plus ancien job queued via FOR UPDATE SKIP LOCKED LIMIT 1, le passe à running, enregistre l'identifiant de la machine exécutante, l'horodatage de début et incrémente le compteur de tentatives. Plusieurs workers ne peuvent pas se voler le même job.
  3. Dispatch (worker, headful) — Le dispatcheur route par kind vers le handler métier, sans jamais évaluer dynamiquement le payload (seuls des paramètres métier typés sont extraits : limit, quantity…). La whitelist des types autorisés doit rester synchronisée entre le worker distant et le CLI côté VPS — tout ajout de kind implique d'éditer les deux composants.
  4. Récupération du code 2FA (worker → VPS → worker) — Pour le scraping nécessitant une authentification forte, le code 2FA n'arrive jamais sur la machine distante par email local : le worker rappelle le VPS via SSH (sous-commande dédiée), qui lit la boîte IMAP côté serveur et renvoie le code brut sur stdout. Le code 2FA n'est jamais loggué en clair (masqué dans les journaux).
  5. Finish (worker → VPS) — Le worker transmet {status, result, error} en un unique objet JSON sur STDIN (jamais en argument shell, ce qui éviterait les ruptures sur newlines ou guillemets). Le CLI de finish écrit le résultat (jsonb, dollar-quoting randomisé), l'erreur et l'horodatage de fin.
  6. Auto-update du worker — En mode boucle, à l'état idle uniquement (jamais en plein job), le worker effectue un git pull --ff-only avec un intervalle minimal de 60 secondes entre deux tentatives, puis se redémarre via os.execv si du code a changé.

6.3 Le portail de commandes SSH (défense si la clé fuit)

La clé SSH du worker est installée côté VPS avec une directive command= pointant vers un portail de validation, assortie des options no-pty, no-agent-forwarding, no-X11-forwarding, no-port-forwarding. Si la machine résidentielle est compromise, la clé n'ouvre pas un shell libre sur le VPS.

Ce portail lit la variable SSH_ORIGINAL_COMMAND, la découpe via shlex.split (jamais bash -c), et n'exécute la commande que si :

  • le préfixe est exactement celui de l'interface CLI navigateur (préfixe strict, immuable) ;
  • le sous-flag appartient à l'ensemble autorisé : --claim, --finish, --get, --enqueue, --malt-code ;
  • chaque valeur de flag satisfait son pattern strict — par exemple, --id accepte uniquement un entier de 1 à 10 chiffres, --status uniquement done ou failed, --kind uniquement un slug alphanumérique avec tirets.

Tout token inconnu ou valeur non conforme entraîne un refus immédiat avec log stderr. C'est aussi pour cette raison que le worker n'utilise jamais de préfixe cd … && dans ses commandes SSH : le portail exige une commande commençant exactement par le préfixe attendu et gère lui-même le répertoire de travail. Pire cas d'une clé volée : polluer la file de jobs — pas exécuter du code arbitraire sur le VPS.

⚠️ Données personnelles & screenshots — Les résultats contenant des noms ou extraits de messages reviennent en jsonb dans la base de données privée, jamais loggués en clair par le worker (seul le compteur l'est). Les screenshots de débogage sont purgés automatiquement en fin de run par le module métier concerné, pas par le worker lui-même ; le worker se contente d'en journaliser le nombre. L'option --keep-screenshots est réservée au débogage local. Par ailleurs, un bug latent est identifié : le timeout de job navigateur (300 s) n'est pas appliqué en pratique, ce qui signifie qu'un job navigateur peut rester bloqué indéfiniment — un correctif est à confirmer.

Boucle d'auto-maintenance de la documentation

Depuis le chantier qui a introduit cette couche, le système dispose d'un pipeline cohérent qui mesure l'écart entre sa propre documentation et son code, puis le corrige de façon autonome avec des garde-fous. Cette boucle constitue la couche d'automates la plus récente et la plus intriquée de l'architecture.

Architecture du pipeline

Régression active non résolue (détectée lors de la relecture du code, 2026-07-19). Un chantier d'élagage antérieur a supprimé trois composants classés à tort comme inactifs, alors qu'ils sont toujours invoqués par l'orchestrateur nocturne. Depuis cette date, trois étapes du pipeline échouent chaque nuit avec un code de retour rc=2 (fichier introuvable). Ce code n'étant pas interprété comme un crash par l'orchestrateur (qui ne considère comme crash que les codes négatifs), l'échec n'est pas remonté en alerte dédiée — il apparaît seulement dans le résumé de sortie. Les étapes concernées sont signalées ⚠️ MORTE dans le schéma ci-dessous. Voir § 7.4, § 7.10, § 7.14 et la section « Dette signalée » pour le détail.

L'orchestrateur nocturne (cron 5 h) enchaîne douze étapes dans l'ordre suivant :

orchestrateur-nocturne (cron 0 5)
┌─ percevoir        → détecteur de dérive doc↔code        (miroir fidélité, lecture seule)
├─ couverture       → détecteur d'angles morts             (complétude de la documentation)
├─ anatomie         → détecteur de dérive d'organes        ⚠️ MORTE — rc=2
├─ graphe-organes   → calculateur d'arêtes inter-organes   (vivant)
├─ réparer          → réparateur de références mortes      (déterministe)
├─ régénérer        → moteur de réécriture profonde        (gaté, LLM)
├─ proposer         → moteur de staging et de brief        ⚠️ MORTE — rc=2
├─ publier-doc      → publieur de chapitres                (gates machine, activation auto)
├─ publier-organes  → publieur d'organes                   ⚠️ MORTE — rc=2
├─ re-percevoir     → détecteur de dérive doc↔code        (re-mesure post-réparation)
├─ soigner          → agrégateur de bilan de santé         (sur l'état FINAL du tick)
└─ publier          → exporteur public assaini             (snapshot → synedre.com)

détecteur de dérive    : cron 0 */6, indépendant (tourne sur son propre rythme)
réparateur mécanique   : cron 10 */6, indépendant (NON orchestré par le tick)
regard externe         : cron */30, indépendant

Le bilan de santé (soigner) est exécuté en fin de tick, après la re-mesure de dérive (re-percevoir), et non juste après la couverture. Ce réordonnancement garantit que le bilan publié sur synedre.com reflète l'état après réparation et régénération, et non l'état de début de nuit.

Le miroir — détecteur de dérive doc↔code (étape : percevoir)

Cron : toutes les six heures (via wrapper). Principe lecture seule stricte : le composant n'écrit jamais dans le code ni dans la documentation ; il inscrit uniquement ses mesures dans les tables de suivi.

Trois types de dérive sont mesurés :

  • code plus récent que la doc — le fichier source lié a été commité après le fichier de documentation : la doc décrit un corps périmé.
  • référence morte — le fichier de documentation cite un fichier (avec numéro de ligne) qui n'existe plus dans le dépôt.
  • chapitre public périmé — la version publiée sur synedre.com est en retard sur le fichier de documentation interne (empreinte SHA-256 non concordante).

Le composant est idempotent : une seule ligne de mesure par chapitre par jour. Il retourne le code 0 si aucune dérive n'est détectée, 1 sinon.

Il est appelé deux fois par l'orchestrateur nocturne : à l'étape percevoir (début du tick) et à l'étape re-percevoir (fin, après réparation et régénération) — la seconde mesure alimente le bilan de santé sur l'état final.

Le détecteur d'angles morts (étape : couverture)

Lancé juste après le miroir. Vérifie la complétude de la documentation — « y a-t-il une partie du système que je ne mentionne jamais ? » —, en complément du miroir qui vérifie la fidélité — « ce que je dis est-il encore vrai ? ».

Méthode : différence d'ensembles.

  • Corps documentable — l'ensemble des composants et tables reconnus du système.
  • Couverture actuelle — ceux nommés dans au moins un chapitre de documentation interne.
  • Angles morts = Corps − Couverture, regroupés par famille.

Un composant isolé non couvert n'est pas nécessairement problématique ; un groupe de composants de même famille non couverts constitue un candidat à une nouvelle section ou un nouveau chapitre (créé par le moteur de réécriture profonde en mode création). Composant lecture seule : n'édite aucun fichier.

Retourne le code 0 si aucun angle mort, 1 sinon. La dimension « couverture » est consolidée dans le bilan de santé.

Le détecteur de dérive d'organes (étape : anatomie) ⚠️ MORTE

Rôle conçu : proprioception de statut des organes — compare le statut déclaré de chaque organe dans le registre public à l'état actif/inactif de l'automate qui le sous-tend, auto-corrige de façon réversible (un organe dont l'automate est inactif passe de construit à partiel). Loi d'asymétrie : ce composant ne monte jamais un statut automatiquement — élever le statut d'un organe reste un geste humain délibéré.

⚠️ Composant introuvable dans le dépôt. Ce script a été supprimé par le chantier d'élagage (2026-07-17), classé à tort comme inactif alors que l'orchestrateur nocturne continue de l'invoquer à l'étape anatomie. Depuis cette date, l'étape échoue systématiquement (rc=2), sans alerte dédiée. Le composant est restaurable via la corbeille système dans la fenêtre de grâce de 30 jours ; la validité de cette fenêtre est à confirmer au moment de la lecture.

Le calculateur d'arêtes inter-organes (étape : graphe-organes)

Dérive et maintient les arêtes reliant les organes entre eux, en deux classes :

  • Arêtes dérivées — prouvables par une requête déterministe, recalculées à chaque exécution (idempotent).
  • Arêtes manuelles — flux conceptuels vrais mais non prouvables par une seule requête ; seedées une fois, jamais écrasées.

Taxonomie des types d'arête :

TypeSignification
fluxProduit ou achemine un flux
composeFait partie d'un ensemble
gouverneCadre sans posséder
veilleSurveille
contraintRègle structurelle
declencheInitie un cycle (impulsion, pas permanence)

Garde-fous :

  • Vérification que la base de données cible est bien la base interne (anti-fuite) — toute écriture vers une autre cible est bloquée.
  • Rejet des labels impliquant une volonté ou un contrôle : le graphe décrit de la plomberie, jamais une intentionnalité.
  • Blocage si une arête proscrite réapparaît.
  • Kill-switch dédié, indépendant du kill-switch global de l'orchestrateur.
  • Mode --test-idempotency : deux exécutions consécutives produisent un état identique.

Le réparateur de références mortes (étape : réparer)

Lancé par l'orchestrateur à l'étape réparer en mode live avec commit, avant la réécriture LLM. Comble le trou entre le miroir (qui diagnostique les chemins morts) et le moteur de réécriture (qui corrige la prose mais ne sait pas qu'un fichier a bougé) : la résolution est déterministe, sans hallucination possible.

Pour chaque référence morte dans un fichier de documentation interne, le composant tente de retrouver l'emplacement réel du fichier par son nom de base :

  • Un seul résultat (certain) → le chemin est réécrit en place.
  • Aucun résultat (fichier supprimé) → signalement + rapport + notification au fondateur (décision humaine requise).
  • Plusieurs résultats (ambigu) → signalement + rapport, jamais de réécriture automatique.

Garde-fous : n'édite que les fichiers de documentation interne — jamais le code, jamais les instructions système, jamais les chapitres publics. Idempotent. Un seul commit par exécution (un seul git revert annule tout). Retourne 0 si rien à faire ou tout résolu, 1 s'il reste des références non résolubles.

À distinguer du réparateur mécanique indépendant (§ ci-après) : ce composant corrige uniquement les références (déterministe, déclenché par l'orchestrateur) ; le réparateur mécanique tente d'autres corrections structurelles et tourne sur son propre cron.

L'agrégateur de bilan de santé (étape : soigner)

Lancé par l'orchestrateur en fin de tick, après l'étape re-percevoir. Agrège cinq dimensions sans en toucher aucune :

DimensionSourceCe qu'elle mesure
ProprioceptionTable de suivi de dériveÉcart doc↔code
CouvertureTable de couverturePart du système jamais documentée
DetteBacklog ouvert P0/P1Volume de dette technique signalée
ApprentissageVue KPI des cicatricesSignal d'incidents résolus
AutomatesJournal d'erreurs cronSanté des tâches planifiées

Écrit un snapshot idempotent dans la table de santé (une ligne par dimension par jour) et génère un item de compte-rendu quotidien. Retourne 0 (OK), 1 (avertissement) ou 2 (critique).

Le réparateur mécanique (indépendant du tick)

Cron : toutes les six heures, décalé de dix minutes par rapport au miroir. Tourne indépendamment de l'orchestrateur nocturne. N'édite que les fichiers de documentation interne — jamais le code, jamais les instructions système, jamais les chapitres publics.

Règle de sûreté : une référence morte n'est corrigée que si son nom de base correspond à exactement un fichier suivi par git. Zéro candidat (composant supprimé) ou deux candidats ou plus (ambiguïté) → proposition au canal technique, sans modification automatique. Réversible : un seul commit par exécution. Plafonné (--max) et doté d'un kill-switch. Mode test (dry-run) par défaut ; l'option --live est requise pour écrire.

Le moteur de réécriture profonde (étape : régénérer)

Appelé par l'orchestrateur avec un budget-mur (défaut : 4 500 secondes). Ouvre une session Claude headless qui : (a) réécrit le fichier de documentation interne en lisant le code réel, (b) produit directement la version publique HTML bilingue FR+EN avec ses métadonnées dans un répertoire temporaire. Le fichier de documentation interne est commité, puis un gate anti-fuite est appliqué avant tout staging public.

Le moteur de staging et de brief (étape : proposer) ⚠️ MORTE

Rôle historique (jusqu'au 2026-07-17) : lancé après la réécriture profonde, ce composant re-stageait les chapitres dont le fichier de documentation interne devançait la version publique, avec un gate anti-fuite indépendant (tout chapitre laissant fuir une donnée sensible était marqué « BLOQUÉ ») et un brief de régénération pour les cas ambigus.

⚠️ Composant introuvable dans le dépôt. Supprimé par le chantier d'élagage (2026-07-17), classé à tort comme inactif alors que l'orchestrateur continue de l'invoquer à l'étape proposer. Depuis cette date, plus aucun chapitre n'est re-stagé automatiquement par ce chemin. Le chemin de publication principal (§ publieur de chapitres ci-après) reste lui actif. Restaurable via la corbeille système dans la fenêtre de grâce de 30 jours ; à confirmer.

L'orchestrateur nocturne (cron 5 h)

Cron : 5 h quotidien (via wrapper). Lance les douze étapes décrites en § Architecture dans l'ordre, sous garde globale :

  • Verrou d'exécution — empêche tout recouvrement si un run précédent est encore en cours.
  • Kill-switch base de données — si l'orchestrateur lui-même est marqué inactif dans le registre des automates, le tick s'arrête immédiatement.
  • Budget-mur configurable pour le moteur de réécriture profonde (défaut : 4 500 s) ; le timeout global du tick est le budget plus 1 800 s.
  • Isolation des étapes — si une étape échoue, le tick continue et note l'échec. Un code retour non-zéro (1 ou 2, y compris les trois étapes mortes) est traité comme un statut, pas un crash ; seul un code négatif (exception dans la boucle de contrôle elle-même) compte comme crash.
  • En fin de tick : appelle l'exporteur public pour pousser un snapshot assaini vers synedre.com.

Options disponibles :

python3 -m <orchestrateur-nocturne> [--dry-run] [--no-deep] [--deep-budget-sec N]

L'exporteur public assaini (étape : publier)

Appelé en fin de tick. Lit les données de la boucle de maintenance depuis la base interne (lecture seule), les assainit (suppression de tout nom de client, adresse IP, secret, chemin interne), puis pousse un snapshot JSON dans la table de flux sur le VPS synedre.com — mise à jour idempotente sur la date du snapshot, avec migration de table automatique à la première exécution. Alimente le terminal de maintenance accessible depuis la section documentation publique.

Le publieur de chapitres (étape : publier-doc)

Deux chemins d'invocation :

  • Cron autonome 3 h 30 — exécution directe avec activation automatique et limite de chapitres par run.
  • Orchestrateur nocturne à l'étape publier-doc — mêmes paramètres. Le tick est ainsi auto-suffisant sans dépendre du cron séparé. C'est aujourd'hui le seul chemin de re-staging effectif, le moteur de staging et de brief étant hors service.

Mécanisme : détecte les chapitres dont l'empreinte SHA-256 source ne correspond plus à la version publiée, les re-stage dans la table de chapitres en attente (transport sécurisé vers la base publique), avec un pré-gate anti-fuite regex multi-couches.

Publication vers le live : via activation manuelle délibérée ou via activation automatique — les chapitres en attente qui passent tous les gates machine (anti-fuite regex, filtre de registre public § VII) sont publiés sans geste humain ; les refusés restent en attente et génèrent une notification au fondateur.

Le publieur d'organes (étape : publier-organes) ⚠️ MORTE

Rôle historique (jusqu'au 2026-07-17) : synchronisait les métadonnées des organes (emoji, nom, statut, position) depuis la base interne vers synedre.com, avec un gate scrub anti-fuite avant écriture. Contrat d'asymétrie explicite : emoji, nom, statut et position sont propagés de façon déterministe automatique ; le sens d'un organe (description, traduction, dimensions qualitatives) n'est jamais propagé automatiquement.

⚠️ Composant introuvable dans le dépôt. Supprimé par le chantier d'élagage (2026-07-17), classé à tort comme inactif alors que l'orchestrateur continue de l'invoquer à l'étape publier-organes. Depuis cette date, le statut, le nom et l'emoji des organes affichés sur synedre.com ne sont plus resynchronisés automatiquement depuis la base interne. Restaurable via la corbeille système dans la fenêtre de grâce de 30 jours ; à confirmer.

Le regard externe (indépendant du tick)

Cron : toutes les trente minutes. Dépile les avis soumis par l'opérateur humain (réponse d'un modèle externe), les évalue via une session Claude sandboxée avec délimiteurs anti-injection, parse sélectivement le verdict JSON (validité globale, détection de tentative d'injection). Si l'avis est jugé valide → signal uniquement (note au compte-rendu quotidien), sans régénération inline. La régénération est consommée par l'orchestrateur nocturne lors de son prochain tick — découplage introduit pour éviter qu'un timeout tue du calcul en cours de run.

Tick d'autonomie

Rôle et cadence

Le tick d'autonomie est un automate cron qui s'exécute toutes les deux minutes en mode actif (--live). Son rôle est de combler le cold-start du pipeline d'autonomie : le moteur de traitement des tâches se self-chaîne à l'intérieur d'un travail en cours, mais rien n'amorce le pipeline si un travail en mode automatique possède des tâches en attente et aucun run actif. Le tick détecte ces travaux dormants et insère un run en statut pending pour la prochaine tâche éligible de chaque chantier concerné.

Note historique : la documentation inline de cet automate et son intitulé CLI décrivent encore une ancienne cadence quotidienne à heure fixe. La doctrine réelle — un tick fréquent auto-limité par une fenêtre horaire configurable — prévaut sur ces mentions périmées.

Fenêtre d'autonomie et quiet-hours

Même en mode actif, le tick ne déclenche aucun seeding en dehors de la fenêtre d'autonomie du jour, lue en base de données et éditable via la commande /fenetre-autonomie. Hors fenêtre, l'automate repasse silencieusement en mode observation. Un pilotage manuel ciblant un chantier précis court-circuite cette fenêtre — geste explicite réservé au fondateur.

Garde-fous de déclenchement

  • Plafond de coût : si le coût cumulé d'un chantier atteint 100 % de son budget alloué, le chantier est gelé et aucun run n'est seedé.
  • Kill-switches d'environnement : deux variables d'environnement permettent de neutraliser totalement l'automate (l'une désactive le mode automatique global, l'autre désactive spécifiquement ce tick) — lorsque l'une ou l'autre est activée, le tick devient une no-op complète.
  • Kill-switches de déploiement : deux variables distinctes permettent de désactiver séparément les étapes de déploiement et de mise en production automatiques, ramenant le tick à un rôle de signalement pur sans action.

Déploiement et mise en production automatiques

En mode actif, le tick peut déclencher les commandes ./deploy et ./ship. La décision est prise site par site selon la configuration de chaque client en base de données. Par défaut un client est éligible ; il peut être explicitement exclu. Pour le ship nocturne non surveillé, une condition supplémentaire s'applique : une preuve QA verte doit exister — sans elle, l'automate émet un signal mais n'exécute pas le ship.

Filet de sécurité post-ship

Après chaque déploiement, la santé de l'application est vérifiée via une porte HTTP sur le port publié. Si la vérification échoue, la version précédente est restaurée automatiquement depuis la sauvegarde locale maintenue à cet effet. Un ancien script de vérification de santé existe dans le dépôt mais est explicitement marqué comme code mort et déprécié — il n'est appelé nulle part dans les chemins actifs.

Sur les ships déclenchés de façon autonome, l'automate conserve systématiquement la sauvegarde locale du build précédent, même après un déploiement jugé sain par la porte HTTP. Ce comportement permet un rollback manuel d'un build fonctionnellement défaillant que la vérification de liveness seule ne peut pas détecter. Les ships manuels conservent le comportement standard (purge de la sauvegarde après santé confirmée).

Plancher de protection immuable

Certains clients sont définis dans le code source comme inconditionnellement exclus du ship automatique, indépendamment de toute configuration en base de données. Cette vérification est effectuée en tout premier, avant toute lecture de la base. La raison : le tick s'exécute via un sous-processus système, hors de toute session interactive — les hooks de protection qui filtrent les invocations interactives ne voient jamais ces appels. Une seule ligne corrompue en base aurait suffi à contourner la protection si ce plancher n'existait pas. Ce plancher est le miroir en Python des hooks de session : même liste de cibles protégées, deux surfaces de code indépendantes.


Garde-fous & opérations

  • Disjoncteur : un wrapper cron désactive automatiquement un automate après 10 crashs consécutifs, avec enregistrement de l'état en base.
  • Audit quotidien : un automate d'audit nocturne relit les journaux d'erreurs et les logs des automates pour produire un rapport de santé.
  • Surveillance des coûts et emballements : trois automates cadencés (toutes les 6, 15 et 30 minutes respectivement) surveillent les coûts, détectent les runaway et émettent des alertes SRE.
  • Sauvegardes & tests de restauration : des dumps nocturnes sont envoyés vers un stockage objet distant (base de données du vaisseau-mère, fichiers, bases des clients distants) ; un automate mensuel teste la restauration effective de ces sauvegardes.
  • Déblocage automatique : un automate cadencé toutes les 15 minutes détecte les runs bloqués et les relance, dans la limite de 4 tentatives, avec kill-switch dédié.
  • Boucle documentaire : un détecteur de dérive, un correcteur automatique et un automate de maintenance nocturne composent le cycle d'auto-réparation documentaire (détaillé à la section précédente).

Dette technique signalée

  • ✅ Cron mort nettoyé : un automate d'audit de fuites d'URL a été supprimé sans remplaçant. La ligne crontab correspondante — qui échouait silencieusement faute de passer par le wrapper standard — a été retirée. Aucune façade ne la remplace à ce jour ; à recréer via le wrapper si le besoin réapparaît.
  • ⚠️ Secret en clair dans le crontab : une entrée crontab embarque un token d'authentification directement dans la ligne de commande. À migrer vers une lecture depuis le fichier d'environnement, selon le pattern déjà utilisé par les autres entrées (lecture du mot de passe base de données par substitution de variable shell).
  • ⚠️ Double exécution de la queue email : un processeur de queue email est déclenché à la fois par le serveur applicatif (toutes les 2 minutes) et par un appel HTTP cron (toutes les minutes). La redondance est sans effet grâce à un LIMIT côté queue, mais reste à nettoyer.
  • ⚠️ Incohérence de casse dans la table des automates : le champ kind (type d'exécution) présente des valeurs avec casse incohérente entre les enregistrements existants. Dette de normalisation à adresser.
  • ⚠️ Absence de verrou générique dans le wrapper cron : les automates non protégés par flock peuvent se chevaucher si leur durée d'exécution dépasse l'intervalle de déclenchement.
  • ✅ Corrigé : cette page indiquait à tort que le tick d'autonomie tournait en mode observation seul. Cette mention était périmée — le mode actif est câblé en crontab depuis la doctrine v3, le tick tourne toutes les 2 minutes et s'autolimite via la fenêtre d'autonomie configurable (voir section précédente).
  • 🔴 Régression élagueur (non alertée) : la boucle de maintenance nocturne appelle trois scripts supprimés lors d'un chantier de nettoyage antérieur. Les étapes correspondantes échouent chaque nuit avec un code de retour indiquant une ressource manquante, absorbé silencieusement (seuls les codes négatifs sont comptabilisés comme crash). Aucune alerte ne s'est déclenchée. Conséquences concrètes : la resynchronisation du statut des composants documentaires vers le site public ne s'effectue plus, et le re-staging documentaire ne passe plus que par le chemin de publication standard. Un quatrième script zombie, appelé par l'automate d'audit de pages, appartient à la même classe de régression. Une fenêtre de grâce de 30 jours depuis la suppression permet une restauration si le besoin est confirmé ; sinon, les appels correspondants doivent être retirés proprement.
  • ⚠️ Bug latent sur le timeout des jobs navigateur : la constante de timeout définie dans le worker navigateur n'est pas appliquée effectivement — un job navigateur peut théoriquement rester en attente indéfiniment. À confirmer et corriger.