Gli eventi sono presi in lease, non trattenuti
Un worker rivendica un evento con un lease. Se lo porta a termine, l’evento è completato e le sue conseguenze vengono accodate prima che la rivendicazione venga rilasciata — così non esiste mai un momento in cui la coda appare vuota mentre del lavoro è ancora in corso. Se il worker muore, il lease scade e un altro worker riprende l’evento. La consegna è at-least-once, e il motore lo dichiara apertamente invece di far intendere un exactly-once.
claim · lease · reclaimGli arrivi di un gather sono righe
L’implementazione ovvia di un fan-in aggiunge elementi a un array su un unico record. Quel record diventa un punto di contesa, e con un fan-out a quaranta vie i perdenti di ogni conflitto di scrittura perdono i propri elementi in silenzio. Qui ogni arrivo è una riga a sé, con un proprio id, e il completamento viene rivendicato prendendo le righe: esattamente un worker le ottiene, gli altri si fermano.
una riga per arrivo, mai un arrayUn join si completa una sola volta, su qualunque nodo
I rami di un join arrivano su worker diversi nello stesso istante, per progetto. L’arrivo che lo completa è deciso da una rivendicazione condizionale e non da un read-and-check, perché un read-and-check è la stessa race condition un livello più su. È qui che il motore ha sbagliato in passato, ed entrambi i bug sono fissati da test che fanno finire insieme dieci rami, per cinque volte di seguito.
conta, poi rivendicaAttendere non costa nulla
Un’approvazione, un segnale in ingresso, un ritardo, un intervallo: ciascuno è una riga con sopra uno stato o un orario. Nessun worker resta parcheggiato, nessun thread viene trattenuto, nessuna memoria viene bloccata. Un’esecuzione può attendere una persona per giorni e sopravvivere al riavvio di ogni replica nel frattempo — e la risposta può arrivare su qualunque replica la persona raggiunga.
wf_wait · wf_timerScalare in orizzontale significa eseguirne di più
Ogni replica esegue lo stesso ciclo sullo stesso database. Non c’è un coordinatore da eleggere né uno shard da assegnare: competono per gli eventi, ed è la rivendicazione a decidere. Anche l’occorrenza di uno schedule viene rivendicata allo stesso modo, così quattro repliche non significano quattro esecuzioni alle 07:00.
coda condivisa, nessun leaderI segreti non stanno nel grafo
Un nodo connettore nomina una credenziale; la credenziale contiene il valore, sigillato, e nessun endpoint lo restituisce — né all’interfaccia, né all’API, né a un agente. È questo che permette allo stesso grafo di essere esportato, revisionato in una pull request e importato in un altro deployment senza che un segreto viaggi insieme a esso.
AES-256-GCM at restOgni esecuzione è un documento, e resta tale
Un’esecuzione conserva il grafo così com’era all’avvio, lo stato per ogni task, il numero di ripetizioni, l’ultimo risultato e l’ultimo errore, il proprio registro eventi e il proprio log con i widget pubblicati riprodotti al suo interno. I record scritti da un grafo portano l’id dell’esecuzione, e gli artefatti portano l’esecuzione che li ha depositati — così il numero su una pagina pubblicata è tracciabile fino all’esecuzione che lo ha prodotto.
il record dell’esecuzioneRuoli e token
I ruoli sono controllati accanto ai route handler, rispecchiati nell’interfaccia, mai solo nell’interfaccia. I token djwf_ con ambito per script, CI e agenti vengono emessi e revocati per singolo consumatore, e una allow-list (WORKFLOW_HTTP_ALLOWLIST) delimita dove un grafo può chiamare, quando ne vuoi una.
admin · utente · visualizzatore · djwf_Limite: consegna at-least-once
Un nodo che muore tra l’aver svolto il proprio lavoro e l’averlo registrato verrà eseguito di nuovo. L’idempotenza è quindi compito di chi lo scrive dove conta, e i nodi connettore del catalogo sono scritti per renderla semplice — upsert invece di insert, artefatti con nome invece che accodati.
dichiarato, non nascostoLimite: il database è il pavimento
Il throughput è limitato da SurrealDB, perché ogni evento, arrivo, attesa e timer è una scrittura. È un compromesso deliberato: le modalità di guasto che elimina valgono più del tetto che impone, per il lavoro a cui questo motore è destinato. Non è il motore giusto per un milione di eventi al secondo.
dichiarato, non nascostoLimite: in sandbox, non isolato
Un nodo JavaScript viene eseguito in un worker Bun con un timeout, che contiene un errore o un ciclo infinito. Non è un confine di sicurezza contro un autore ostile, e sono i controlli del deployment stesso — chi può scrivere un grafo — a occupare quel posto. I nodi per file locali sono confinati a una root configurata, e disattivati, non illimitati, quando quella root non è impostata.
dichiarato, non nascosto