Tutorial - Read security refusals
A refusal is observable evidence that execution did not cross a declared
boundary. Here a deliberately adversarial provider requests a capability outside
uses. The runtime must refuse it before the administrative skill starts.
Prerequisites: Touba and Node.js 24. No SDK, paid model or network is needed.
1. Create the program
In a macOS/Linux terminal:
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
The module declares two skills, but the agent is authorized to use only one. The presence of HiddenAdminPath in the source does not authorize its selection.
2. Propose an unauthorized target
This provider intentionally violates the contract. It is test code, not a recommended decision policy. No instruction in its prose can grant authority.
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
Expected: UNAUTHORIZED_CAPABILITY_REFUSED_OK. The Touba command exits with
1; the verification script succeeds because it checks that exact refusal.
There is no business result, no administrative message and no selected capability.
3. Interpret other refusal families
| Boundary | Observable check |
|---|---|
| caller authority | compile diagnostic or agent invocation refusal |
| pure function | interaction/approval rejected at compilation |
| memory | source scopes and runtime access refusal |
| secret | handle, grant and agent scope before handler dispatch |
| payload | typed input/output validation |
| human approval | rejected decision stops the continuation |
| budget | no further operation after the effective limit |
See scoped memory, secrets
and human interaction for complete recipes.
Authorization refusals are not business errors to recover through on failure.
A refusal after a previous external write does not undo that earlier effect.
This example tests enforcement, not model reliability, OS isolation or cryptographic signing. The trace retains structural references and reasons, not arbitrary business payloads or resolved secret values.