Les événements sont loués, pas détenus
Un worker revendique un événement au moyen d’un bail. S’il termine, l’événement est marqué complet et ses conséquences sont mises en file avant que la revendication ne soit libérée — il n’existe donc jamais d’instant où la file paraît vide alors qu’un travail est encore en cours. Si le worker meurt, le bail expire et un autre worker reprend l’événement. La livraison se fait au moins une fois, et le moteur le dit clairement plutôt que de laisser croire à une livraison exactement une fois.
revendication · bail · repriseLes arrivées d’un rassemblement sont des lignes
L’implémentation évidente d’une convergence consiste à ajouter à un tableau sur un seul enregistrement. Cet enregistrement devient un point de contention, et sous une divergence à quarante branches, les perdants de chaque conflit d’écriture perdent silencieusement leurs éléments. Ici, chaque arrivée est sa propre ligne avec son propre identifiant, et l’achèvement est revendiqué en prenant les lignes : un seul worker les obtient, les autres se retirent.
une ligne par arrivée, jamais un tableauUne jonction s’achève une fois, sur n’importe quel nœud
Les branches d’une jonction arrivent sur des workers différents au même instant, par conception. L’arrivée qui l’achève est décidée par une revendication conditionnelle plutôt que par une lecture-puis-vérification, car une lecture-puis-vérification n’est que la même course déplacée d’un cran. C’est ici que le moteur s’est trompé par le passé, et les deux bugs sont épinglés par des tests qui font terminer dix branches ensemble, cinq fois de suite.
compter, puis revendiquerAttendre ne coûte rien
Une approbation, un signal entrant, un délai, un intervalle : chacun est une ligne portant un statut ou une heure. Aucun worker n’est mis en attente, aucun thread n’est retenu, aucune mémoire n’est immobilisée. Une exécution peut attendre une personne pendant des jours et survivre au redémarrage de chaque réplique entre-temps — et la réponse peut arriver sur n’importe quelle réplique que cette personne atteint.
wf_wait · wf_timerMonter en charge, c’est en exécuter davantage
Chaque réplique exécute la même boucle contre la même base de données. Il n’y a pas de coordinateur à élire ni de fragment à attribuer : elles se disputent les événements, et c’est la revendication qui tranche. Une occurrence de planification est revendiquée de la même façon, si bien que quatre répliques ne signifient pas quatre exécutions à 07:00.
file partagée, pas de leaderLes secrets ne sont pas dans le graphe
Un nœud connecteur nomme un identifiant ; l’identifiant détient la valeur, scellée, et aucun point de terminaison ne la renvoie — ni à l’interface, ni à l’API, ni à un agent. C’est ce qui permet au même graphe d’être exporté, revu dans une pull request et importé dans un autre déploiement sans qu’un secret ne voyage avec lui.
AES-256-GCM au reposChaque exécution est un document, et le reste
Une exécution conserve le graphe tel qu’il était à son démarrage, le statut de chaque tâche, le nombre de répétitions, le dernier résultat et la dernière erreur, son registre d’événements, et son journal avec les widgets publiés rejoués à l’intérieur. Les enregistrements qu’un graphe a écrits portent l’identifiant d’exécution, et les artefacts portent l’exécution qui les a déposés — si bien que le chiffre affiché sur une page publiée peut être retracé jusqu’à l’exécution qui l’a produit.
l’enregistrement d’exécutionRôles et jetons
Les rôles sont vérifiés à côté des gestionnaires de route, reflétés dans l’interface, jamais uniquement dans l’interface. Les jetons djwf_ à portée limitée pour les scripts, la CI et les agents sont émis et révoqués par consommateur, et une liste d’autorisation (WORKFLOW_HTTP_ALLOWLIST) borne les destinations qu’un graphe peut appeler, quand vous en voulez une.
administrateur · utilisateur · lecteur · djwf_Limite : livraison au moins une fois
Un nœud qui meurt entre l’exécution de son travail et son enregistrement sera relancé. L’idempotence est donc la responsabilité de l’auteur là où elle compte, et les nœuds connecteurs du catalogue sont écrits pour la rendre facile — des upserts plutôt que des insertions, des artefacts nommés plutôt qu’ajoutés.
annoncée, pas cachéeLimite : la base de données est le plancher
Le débit est borné par SurrealDB, car chaque événement, arrivée, attente et minuterie est une écriture. C’est un compromis délibéré : les modes de défaillance qu’il élimine valent plus que le plafond qu’il impose pour le travail auquel ce moteur est destiné. Ce n’est pas le bon moteur pour un million d’événements par seconde.
annoncée, pas cachéeLimite : en bac à sable, pas isolé
Un nœud JavaScript s’exécute dans un worker Bun avec un délai d’expiration, ce qui contient une erreur et une boucle infinie. Ce n’est pas une frontière de sécurité contre un auteur malveillant, et ce sont les propres contrôles du déploiement — qui a le droit d’écrire un graphe — qui tiennent ce rôle. Les nœuds de fichiers locaux sont confinés à une racine configurée, et désactivés plutôt que laissés sans restriction lorsque cette racine n’est pas définie.
annoncée, pas cachée