Trigger réel gouverné : revue matinale
Ce tutoriel montre comment lancer une exécution Touba automatiquement avec un
trigger schedule -> strategy. Le scénario se concentre sur le déclenchement,
les limites et la trace avant d’ajouter des tools réels.
Vous allez construire une revue matinale qui se déclenche après environ une seconde, affiche deux messages, retourne un titre et écrit une trace.
Ce que vous allez apprendre
| Étape | Ce que vous vérifiez |
|---|---|
Source .tb | Le trigger déclare quand lancer une stratégie et avec quels arguments |
| AIR | Le trigger devient un artefact runtime, pas un commentaire |
| Manifest | L’hôte active explicitement le trigger autorisé |
| Runner | touba trigger run attend l’intervalle puis lance la stratégie |
| État durable de trigger | touba.triggers.sqlite garde les event_id consommés |
| Trace | Le runtime enregistre réception, acceptation, début et fin d’exécution |
| Refus | Un replay ou un trigger désactivé est refusé avant exécution |
1. Créer le dossier
mkdir -p morning-trigger-demo
cd morning-trigger-demo
Créez ensuite le fichier source.
module examples.trigger_morning_review
define ReviewTopic as Text = "signaux produit Touba"
define RiskTheme as Text = "risques agents"
function BuildReviewTitle
takes topic as Text
gives Text
do
give "Revue matinale: " ++ topic
skill BuildMorningBrief
description "Build and display the morning review for the supplied topic."
takes topic as Text
gives Text
do
BuildReviewTitle
with topic as topic
into title
show
with message as title
show
with message as "Point de controle: verifier traces, refus et prochaines actions"
give title
strategy RunMorningReview
takes topic as Text
gives Text
uses BuildMorningBrief
do
bind scoped_topic as Text = topic ++ " / " ++ RiskTheme
BuildMorningBrief
with topic as scoped_topic
into title
give title
trigger MorningReview
on schedule "every 1s"
runs strategy RunMorningReview
with topic as ReviewTopic
limits
max_runs_per_day as 1
timeout_ms as 30000
Ce fichier contient quatre idées distinctes :
definenomme des valeurs non sensibles au niveau du module ;functionconstruit un titre sans effet externe ;skillaffiche le résultat et retourne une valeur ;triggerlance la stratégie selon un intervalle borné.
Le trigger utilise le standard du runner foreground :
every <n>s
every <n>m
every <n>h
Un cron complet n’est pas exécuté par le runner.
2. Compiler vers AIR
touba compile morning-review.tb --out morning-review.air.json
À ce stade, rien n’est exécuté. La compilation produit l’AIR : le contrat que le runtime va consommer.
Vous pouvez inspecter l’AIR :
touba inspect morning-review.air.json
Vous devez y voir une stratégie RunMorningReview et un trigger MorningReview.
3. Activer le trigger
Un trigger Touba n’est pas actif simplement parce qu’il existe dans le source. L’hôte doit l’autoriser dans un manifest.
Créez touba.manifest.json :
{
"manifest_version": "touba-manifest-v1",
"triggers": {
"trigger:examples.trigger_morning_review/MorningReview": {
"enabled": true,
"source": {
"kind": "schedule"
},
"limits": {
"max_runs_per_day": 1,
"timeout_ms": 30000
}
}
}
}
Le manifest dit trois choses :
- ce trigger est activé dans cet environnement ;
- la source autorisée est
schedule; - les limites de l’hôte n’élargissent pas celles du source
.tb.
4. Lancer le trigger réel
touba trigger run morning-review.air.json \
--manifest touba.manifest.json \
--trigger trigger:examples.trigger_morning_review/MorningReview \
--max-runs 1 \
--state touba.triggers.sqlite \
--trace-out morning-review.trace.json
La commande attend l’intervalle every 1s, génère un événement normalisé, puis
lance la stratégie.
Sortie attendue :
Revue matinale: signaux produit Touba / risques agents
Point de controle: verifier traces, refus et prochaines actions
Le runner s’arrête après un run parce que --max-runs 1 est explicite. Sans
--max-runs, la CLI refuse de démarrer. Sans --state, le registre est créé
dans .touba/triggers.sqlite.
--state crée ou met à jour un état durable de trigger. Cet état ne contient
pas de payload brut ; il conserve seulement les event_id, les statuts
acceptés/refusés, les raisons de refus et la clé de jour UTC utilisée pour
appliquer max_runs_per_day. Le chemin passé à --state doit se terminer par
.triggers.sqlite.
5. Lire la trace
La trace est écrite dans morning-review.trace.json.
touba inspect morning-review.trace.json
touba trigger state touba.triggers.sqlite --trigger trigger:examples.trigger_morning_review/MorningReview
La trace doit rendre visibles les événements suivants :
trigger_event_received
trigger_event_accepted
trigger_execution_started
user_interaction_shown
trigger_execution_completed
run_completed
Ce point est important : l’affichage terminal n’est pas la seule preuve. La trace reste l’artefact d’audit du run.
L’inspection de touba.triggers.sqlite doit afficher un trigger, une
acceptation et un event_id consommé.
6. Refuser un replay explicite
Le runner produit ses propres event_id. Pour tester un replay de manière
contrôlée, envoyez deux fois le même événement explicite avec un état dédié à
ce test. On utilise un fichier séparé pour ne pas réutiliser le quota déjà
consommé par le runner.
Premier passage :
touba run morning-review.air.json \
--manifest touba.manifest.json \
--trigger-event-json '{"trigger_ref":"trigger:examples.trigger_morning_review/MorningReview","event_id":"evt-demo-replay","source_kind":"schedule"}' \
--state replay.triggers.sqlite \
--output trace
Deuxième passage avec le même event_id :
if touba run morning-review.air.json \
--manifest touba.manifest.json \
--trigger-event-json '{"trigger_ref":"trigger:examples.trigger_morning_review/MorningReview","event_id":"evt-demo-replay","source_kind":"schedule"}' \
--state replay.triggers.sqlite \
--output json > replay-result.json; then
exit 1
else
test "$?" -eq 1
fi
node --input-type=module -e 'import assert from "node:assert/strict"; import {readFileSync} from "node:fs"; const r=JSON.parse(readFileSync("replay-result.json","utf8")); assert.equal(r.reason,"TriggerReplayRejected"); assert.ok(!r.trace.events.some(e=>e.kind==="trigger_execution_started")); console.log("TRIGGER_REPLAY_REFUSED_OK");'
Résultat attendu :
trigger error [TriggerReplayRejected]
La stratégie n’est pas relancée. La trace contient trigger_event_refused avec
la raison event_replay.
7. Tester les checks sans exécuter
Le dry-run vérifie le trigger, le manifest et les limites sans lancer la stratégie.
touba run morning-review.air.json \
--manifest touba.manifest.json \
--trigger-event-json '{"trigger_ref":"trigger:examples.trigger_morning_review/MorningReview","event_id":"evt-demo-001","source_kind":"schedule"}' \
--trigger-dry-run \
--output trace
Vous devez obtenir un rapport de checks acceptés, pas une trace d’exécution. Aucun message show n’est
affiché, car la stratégie n’est pas exécutée en dry-run.
8. Provoquer un refus volontaire
Créez un manifest désactivé :
{
"manifest_version": "touba-manifest-v1",
"triggers": {
"trigger:examples.trigger_morning_review/MorningReview": {
"enabled": false,
"source": {
"kind": "schedule"
},
"limits": {
"max_runs_per_day": 1,
"timeout_ms": 30000
}
}
}
}
Puis lancez :
if touba trigger run morning-review.air.json \
--manifest touba.manifest.disabled.json \
--trigger trigger:examples.trigger_morning_review/MorningReview \
--max-runs 1 \
--state touba.triggers.sqlite \
--output json > disabled-result.json; then
exit 1
else
test "$?" -eq 1
fi
node --input-type=module -e 'import assert from "node:assert/strict"; import {readFileSync} from "node:fs"; const r=JSON.parse(readFileSync("disabled-result.json","utf8")); assert.equal(r.reason,"TriggerDisabled"); assert.deepEqual(r.messages,[]); console.log("DISABLED_TRIGGER_REFUSED_OK");'
Résultat attendu :
trigger error [TriggerDisabled]
Le runtime refuse avant d’entrer dans RunMorningReview. Aucun message show
n’est produit.
9. Ce que vous avez réellement testé
Ce tutoriel teste une première autonomie réactive :
- le programme Touba déclare le déclenchement ;
- l’environnement autorise explicitement ce déclenchement ;
- le runner foreground attend un intervalle réel ;
- l’état durable de trigger refuse un replay explicite ;
- le runtime applique les checks avant l’exécution ;
- la trace documente l’acceptation ou le refus.
Suite
Pour les triggers request -> strategy avec service webhook HTTP, queue
persistante, état durable, signature HMAC et dead-letter, voir
Triggers gouvernés.
Ce scénario établit le modèle schedule -> strategy : déclenchement déclaré,
état durable, refus de replay et trace exploitable.
Pour plusieurs runs planifiés, --output json retourne un tableau avec le statut et la raison de chaque run. La trace agrégée possède son identité ; origin_run_id et origin_event_id conservent la provenance des événements enfants. trigger_runner_run_started et trigger_runner_run_completed délimitent chaque enfant. Le runner s’arrête à la première pause, interruption, annulation ou erreur ; il ne poursuit pas silencieusement et n’annonce pas un succès global.