send_outreach governs one application. A rule that reads
external_effect governs every application whose operator has labelled their
tools. Labels are what make a rule portable.
The rule holds the logic. The config holds the facts about your app.
The five labels
Declare them innanny.toml, per tool:
A tool can carry several.
send_payment_confirmation is plausibly
external_effect and moves_money at once.
Reading labels in a rule
ctx.tool_labels carries labels for every allowed tool, not just the pending
one, because rules routinely need to ask what an already-called tool was.
Two helpers
tool_has returns False for an unknown tool and for an unknown label. That is
the safe answer in both directions: a rule asking about a tool the operator
never declared should not fire, and a misspelled label must not silently match
everything.
Labelling is the operator’s job, not the rule author’s
Only you know that yourfetch_ticket pulls in customer-written text. A rule
author cannot know that, which is exactly why the split exists. An unlabelled
tool is invisible to every label-based rule, so it is governed by the allowlist
and by nothing else.
“Declared with no labels” and “never declared” are different answers, and Nanny
keeps them distinct: an allowed tool always appears in tool_labels, with an
empty list if it carries none.