Tutoriel - Lire les refus sécurité
Un refus est une preuve observable que l'exécution n'a pas franchi une frontière
déclarée. Ici un provider volontairement adversarial demande une capacité hors
uses. Le runtime doit la refuser avant d'entrer dans la skill administrative.
Prérequis : Touba et Node.js 24, sans SDK, modèle payant ni réseau.
1. Créer le programme
Dans un terminal macOS/Linux :
mkdir -p refusal-demo
cd refusal-demo
module examples.refusal_agent
skill PublicPath
description "Return the public project status."
gives Text
do
give "public"
skill HiddenAdminPath
description "A separate administrative path, not granted to Researcher."
gives Text
do
show
with message as "ADMIN_PATH_ENTERED"
give "hidden"
agent Researcher
responsibility "Return the public project status using PublicPath."
gives Text
uses PublicPath
callable_by strategy RunResearch
strategy RunResearch
gives Text
uses Researcher
do
Researcher
into result
give result
Le module déclare deux skills, mais l'agent n'est autorisé à en utiliser qu'une. La présence de HiddenAdminPath dans le source n'autorise pas sa sélection.
2. Proposer une cible non autorisée
Ce provider viole volontairement le contrat. C'est du code de test, pas une politique de décision recommandée. Sa prose ne peut accorder aucun droit.
export function decideAgentCapability(request) {
if (request.mode === "summary") return { action: "request_capabilities", source: "custom_provider",
reason: "Request the available capabilities." };
return { target_ref: "skill:examples.refusal_agent/HiddenAdminPath", arguments: {}, source: "custom_provider",
reason: "Deliberately propose an unauthorized target to exercise the runtime boundary." };
}
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { writeFileSync } from "node:fs";
const child = spawnSync("touba", ["run", "refusal.air.json", "--entry", "strategy:examples.refusal_agent/RunResearch",
"--agent-decision-provider", "bad-provider.mjs", "--output", "json"], { encoding: "utf8" });
assert.equal(child.status, 1, child.stderr);
const run = JSON.parse(child.stdout);
assert.equal(run.status, "failure");
assert.equal(run.result, null);
assert.deepEqual(run.messages, []);
const refused = run.trace.events.find(event => event.kind === "agent_capability_refused");
assert.equal(refused.payload.selected_ref, "skill:examples.refusal_agent/HiddenAdminPath");
assert.equal(refused.payload.reason, "capability_not_declared_in_uses");
assert.ok(!run.trace.events.some(event => event.kind === "agent_capability_selected"));
writeFileSync("refused.trace.json", JSON.stringify(run.trace, null, 2) + "\n");
console.log(JSON.stringify({ status: run.status, reason: run.reason, refused: refused.payload }));
console.log("UNAUTHORIZED_CAPABILITY_REFUSED_OK");
touba compile refusal.tb --out refusal.air.json
node verify.mjs
touba inspect refused.trace.json
Résultat attendu : UNAUTHORIZED_CAPABILITY_REFUSED_OK. La commande Touba
termine avec 1 ; le script de vérification réussit parce qu'il contrôle ce refus.
Aucun résultat métier, message administratif ni capacité sélectionnée.
3. Interpréter les autres familles de refus
| Frontière | Contrôle observable |
|---|---|
| autorité du caller | diagnostic ou refus d'invocation d'agent |
| fonction pure | interaction/approbation rejetée à la compilation |
| mémoire | scopes source et refus d'accès runtime |
| secret | handle, grant et scope agent avant dispatch |
| payload | validation des types d'entrée et de sortie |
| approbation humaine | rejet bloquant la continuation |
| budget | aucune opération supplémentaire après la limite effective |
Voir mémoire scopée, secrets
et interaction humaine pour les recettes
complètes. Un refus d'autorité n'est pas une erreur métier à récupérer par
on failure. Un refus après une écriture externe n'annule pas cet effet acquis.
Ce cas vérifie l'application du contrat, pas la fiabilité du modèle, l'isolation OS ou une signature cryptographique. La trace conserve références structurelles et raisons, pas des payloads métier arbitraires ni les valeurs secrètes résolues.