Triggers gouvernés
Un trigger Touba transforme un événement en exécution gouvernée.
Deux chemins V1 sont disponibles :
schedule -> strategy
request -> strategy
Le premier utilise un runner foreground borné. Le second utilise un service webhook HTTP explicite, avec queue persistante, état TD-1 et trace. Les deux chemins passent par AIR, manifest et vérifications runtime avant d’exécuter la stratégie.
1. Écrire le programme
module examples.daily_research
strategy DailyResearch
takes topic as Text
do
show
with message as "Daily research run: " ++ topic
trigger DailyResearchSchedule
on schedule "every 1s"
runs strategy DailyResearch
with topic as "daily review"
limits
max_runs_per_day as 1
timeout_ms as 30000
trigger DailyResearchWebhook
on request POST "/webhook/daily-research"
runs strategy DailyResearch
with topic as payload.topic
limits
max_runs_per_day as 10
timeout_ms as 30000
Le runner accepte ce format :
every <n>s
every <n>m
every <n>h
Le support cron complet appartient à une couche de planification ultérieure.
2. Compiler
touba compile src/examples/daily_research.tb --out daily_research.air.json
L’AIR contient la déclaration du trigger, sa cible et ses limites. Il ne contient pas de scheduler, de secret ou de payload externe.
payload.topic est un binding dynamique du champ de l’événement, pas une valeur
factice ni une valeur de repli. Le mapping du manifest doit couvrir ce même
argument ; un champ manquant est refusé avant l’exécution.
3. Activer le trigger dans le manifest
{
"manifest_version": "touba-manifest-v1",
"bindings": {},
"triggers": {
"trigger:examples.daily_research/DailyResearchSchedule": {
"enabled": true,
"source": {
"kind": "schedule"
},
"limits": {
"max_runs_per_day": 1,
"timeout_ms": 30000
}
},
"trigger:examples.daily_research/DailyResearchWebhook": {
"enabled": true,
"source": {
"kind": "request"
},
"payload": {
"format": "json",
"content_types": ["application/json"],
"max_bytes": 4096,
"map": {
"topic": "$.topic"
}
},
"limits": {
"max_runs_per_day": 10,
"timeout_ms": 30000
}
}
}
}
Sans cette entrée, le runtime refuse le trigger avant d’exécuter la stratégie.
4. Lancer le trigger réel
touba trigger run daily_research.air.json \
--manifest touba.manifest.json \
--trigger trigger:examples.daily_research/DailyResearchSchedule \
--max-runs 1 \
--state touba.triggers.sqlite \
--trace-out daily_research.trace.json
Résultat attendu après environ une seconde :
Daily research run: daily review
--max-runs fixe le nombre d’exécutions. Le registre SQLite par défaut est
.touba/triggers.sqlite, dans le projet ou à côté de l’AIR hors projet.
--state permet de choisir un autre fichier .sqlite.
La trace écrite dans daily_research.trace.json contient l’événement reçu, l’acceptation du trigger, le début d’exécution et la fin du run.
Le registre SQLite mémorise les event_id consommés, les refus
observés et la clé de jour UTC utilisée pour appliquer max_runs_per_day, sans
payload brut. Si un même événement est rejoué, Touba refuse avant de relancer la
stratégie avec TriggerReplayRejected. Une acceptation enregistrée un jour
précédent ne bloque pas le premier événement du jour courant. Le chemin passé à
--state doit se terminer par .triggers.sqlite.
5. Lancer le service webhook
touba trigger serve daily_research.air.json \
--manifest touba.manifest.json \
--state touba.triggers.sqlite \
--queue touba.trigger-queue.sqlite \
--trace-dir traces \
--max-events 1
Dans un autre terminal :
curl -X POST http://127.0.0.1:8787/webhook/daily-research \
-H 'content-type: application/json' \
-H 'x-touba-event-id: evt-webhook-001' \
-d '{"topic":"industrial adoption"}'
Le service répond 202 queued, inscrit une entrée dans
touba.trigger-queue.sqlite, exécute la stratégie, écrit l’état TD-1 et produit
une trace par entrée si --trace-dir est fourni. Le corps HTTP brut n’est pas
injecté comme input Touba implicite et n’est pas écrit dans la queue, l’état ou
la trace ; la queue conserve la taille, le digest SHA-256 du payload et les
arguments explicitement mappés par le manifest. Les champs non listés dans
payload.map sont ignorés.
Pour exposer ce webhook à un fournisseur externe, ajouter
--signature-secret-env TOUBA_TRIGGER_SIGNATURE_SECRET et envoyer
x-touba-signature: sha256=<hmac> calculé sur le corps HTTP exact.
Sur une interface non loopback, ajouter --auth-token-env TOUBA_TRIGGER_TOKEN et
envoyer Authorization: Bearer <token> ou x-touba-trigger-token: <token>.
6. Suivre le statut des triggers
curl http://127.0.0.1:8787/status
Le service répond avec touba-trigger-status-v1. Cette sortie donne une vue
compacte des triggers déclarés : type request ou schedule, statut, limites,
compteurs d’acceptation/refus et état de queue. Elle ne contient ni token
d’authentification, ni secret HMAC, ni payload HTTP brut. Quand le service est
protégé par token, /status demande la même authentification que les routes
webhook.
7. Tester les checks sans exécuter
touba run daily_research.air.json \
--manifest touba.manifest.json \
--trigger-event-json '{"trigger_ref":"trigger:examples.daily_research/DailyResearchSchedule","event_id":"evt-001","source_kind":"schedule"}' \
--trigger-dry-run
Résultat attendu :
- le trigger existe dans l’AIR ;
- le manifest l’active ;
- la source événementielle correspond ;
- les limites du manifest n’élargissent pas celles de l’AIR ;
- la stratégie cible n’est pas lancée en dry-run.
8. Exécuter avec un événement explicite
touba run daily_research.air.json \
--manifest touba.manifest.json \
--trigger-event-json '{"trigger_ref":"trigger:examples.daily_research/DailyResearchSchedule","event_id":"evt-002","source_kind":"schedule"}' \
--output trace
Ce mode sert aux tests d’hôte ou d’adapter. Il fournit l’événement à la main au lieu d’attendre l’intervalle déclaré.
9. Inspecter
touba inspect daily_research.air.json
touba inspect trace.json
touba trigger state touba.triggers.sqlite --trigger trigger:examples.daily_research/DailyResearchSchedule
touba inspect daily_research.air.json --manifest touba.manifest.json
L’inspection affiche ce qui existe dans les artefacts fournis. Pour l’état
durable de trigger (TD-1), elle affiche les compteurs d’acceptations, de refus et
d’event_id consommés. Le service webhook existe quand il est lancé
explicitement par touba trigger serve ; la supervision de déploiement relève
de l’environnement hôte.
Conservation et migration
L’admission du déclencheur est enregistrée avant le lancement de sa stratégie. Un arrêt du processus conserve ainsi l’identité consommée. Les compteurs sont calculés par jour UTC ; les identifiants anti-rejeu restent conservés après archivage.
Le registre est limité à 512 Mio par défaut. --state-max-bytes ajuste ce
plafond (minimum 65 536 octets). Un registre plein bloque une nouvelle admission.
touba trigger state touba.triggers.sqlite --limit 100
touba trigger state touba.triggers.sqlite --after 100 --limit 100
Utilisez la valeur next renvoyée comme curseur de la page suivante.
--trigger <référence> filtre les événements et ajoute les compteurs du trigger.
touba trigger archive-state touba.triggers.sqlite --before 2026-10-01T00:00:00Z
touba trigger archive-state touba.triggers.sqlite --before 2026-10-01T00:00:00Z \
--out archives/triggers-octobre.sqlite --apply
La première commande prévisualise. La seconde copie puis vérifie l’archive
avant de retirer les détails des événements réussis et des refus. Les événements
actifs, suspendus ou d’issue incertaine, les compteurs et les identifiants
consommés restent dans le registre. L’archive se consulte avec touba trigger state.
Pour convertir un ancien état JSON, arrêtez son service puis lancez :
touba trigger migrate-state ancien.trigger-state.json --out touba.triggers.sqlite
touba trigger migrate-state ancien.trigger-state.json --out touba.triggers.sqlite --apply
La migration crée un nouveau registre et une copie exacte .source-backup.json.
Elle conserve le fichier d’origine et refuse d’écraser une destination existante.
Les états migrés restent d’issue incertaine, car l’ancien registre conservait
l’admission mais pas le résultat final. Aucun événement n’est relancé.
Refus utiles
| Situation | Comportement attendu |
|---|---|
| Trigger absent de l’AIR | refus avant exécution |
| Trigger désactivé dans le manifest | refus avant exécution |
| Source événementielle différente | refus avant exécution |
| Manifest qui élargit une limite AIR | refus avant exécution |
event_id absent | refus avant exécution |
event_id déjà consommé dans --state | refus replay avant exécution |
Service webhook sans --queue | file SQLite dans .touba/trigger-queue.sqlite |
| Payload request non conforme au manifest | refus avant mise en queue |
| Signature webhook invalide | refus avant mise en queue |
| Service webhook exposé hors loopback sans token | refus CLI avant démarrage |
Portée
- Le runner foreground exécute des entrées planifiées gouvernées.
- Le service webhook exécute les entrées
request -> strategy. - La queue durable suit
touba-trigger-queue-v1. - Le statut compact du service suit
touba-trigger-status-v1. - Le manifest active explicitement chaque trigger.
- L’état de trigger enregistre les
event_idconsommés et les refus de replay. - Les contrats de payload du manifest mappent des champs JSON explicites vers les arguments de la stratégie cible.
- Les signatures HMAC, retries bornés, reprises d’entrées
processinget états dead-letter sont supportés par le service webhook. - Les schedulers distribués, queues distribuées, adapters cloud, auto-discovery de manifest, déclenchements directs d’agents et garanties réglementaires relèvent des couches plateforme environnantes.