Policy

Policy: which tools may run, which need a person's approval.

noclick.Policy.set(deny=["*.delete_*"], require_approval=["gmail.send_email_message"])
noclick.Policy.set(allow=["search", "gmail.*"], user="user_42")   # narrows the account policy

Patterns match dotted tool names (gmail.*, *.delete_*, search). Deny wins; an empty allow allows everything the other rules don't stop. Like every call, get, set and evaluate follow as_user: inside it they address that user's policy, and user=None addresses the account's. A rule can also be a noclick.Rule with a condition on the call's arguments and a person who approves it.

class Policy

The account's policy, or one user's (user_id). Each list holds patterns, and Rules where a rule has a condition or an approver.

Attributes
allow(string | Rule)[]

Patterns, and Rules where a rule has a condition or an approver.

deny(string | Rule)[]
requireApproval(string | Rule)[]
userIdstring | null

Whose policy this is (your id for the user; null for the account's).

rawJsonObject

The response JSON exactly as the API sent it.

liststatic

static list(options?: { client?: NoClick }): Promise<Policy[]>

The account's policy and every user policy.

Parameters
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

The account's policy first (its user_id is None), then each user that has a policy of its own, with your id for that user as its user_id.

evaluatestatic

static evaluate(
  tools: string | readonly string[],
  options?: PolicyOptions & { draft?: PolicyDraft; arguments?: JsonObject },
): Promise<PolicyVerdict[]>

How the policy judges each of tools (dotted or wire names, at most 500) for the account, or { user }. A draft is judged in place of its layer's saved rules. Nothing runs and no approval is requested. arguments judges one tool's call with those arguments, so rules with a when apply; without them a rule with a when never matches.

Parameters
toolsstring | readonly string[]required
options.argumentsJsonObject
options.userUserOption

The user whose policy this is (a User or your id for one); omitted = the asUser user, else the account's; null = the account's.

(defaults to the current user)
options.draftPolicyDraft
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

One verdict per tool, in order: allow, deny or approval, with the matched_pattern and layer that decided it, and the whole rule. matched_pattern is None when no rule matched, or when an allow list left the tool out.

getstatic

static get(options?: PolicyOptions): Promise<Policy>

The account's policy, or with { user } that user's own rules.

Parameters
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

options.userUserOption

The user whose policy this is (a User or your id for one); omitted = the asUser user, else the account's; null = the account's.

Returns

The allow, deny and require_approval lists, empty where nothing is set. A user's policy holds only its own rules, not the account's.

setstatic

static set(rules: PolicyRules, options?: PolicyOptions): Promise<Policy>

Replace the policy: lists left out are sent empty.

Parameters
rulesPolicyRulesrequired
options.userUserOption

The user whose policy this is (a User or your id for one); omitted = the asUser user, else the account's; null = the account's.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

The saved policy, its patterns lowercased and deduplicated. A list you left out is now empty.

addstatic

static add(
  rules: readonly RuleInput[],
  options?: PolicyOptions & { to?: "requireApproval" | "deny" | "allow" },
): Promise<Policy>

Add rules to one list (to: requireApproval by default, deny or allow) of the account's policy (or { user }'s), leaving every other rule as it is, in one change on the server. A rule for a tool that list already has replaces it, so code that owns a rule can add it on every run.

await Policy.add(["quickbooks.create_bill"]);
await Policy.add(["slack.*"], { to: "deny", user: "user_42" });
Parameters
rulesreadonly RuleInput[]required
options.to"requireApproval" | "deny" | "allow"
options.userUserOption

The user whose policy this is (a User or your id for one); omitted = the asUser user, else the account's; null = the account's.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

The saved policy, with the rest of its rules.

removestatic

static remove(
  tools: readonly string[],
  options?: PolicyOptions,
): Promise<Policy>

Remove the rules for these tools, as written, from every list of the account's policy (or { user }'s), leaving the rest. A tool with no rule changes nothing.

Parameters
toolsreadonly string[]required
options.userUserOption

The user whose policy this is (a User or your id for one); omitted = the asUser user, else the account's; null = the account's.

(defaults to the current user)
options.clientNoClick

The client to send with; defaults to the module client built from NOCLICK_API_KEY.

Returns

The saved policy, with the rules that are left.

PolicyVerdict

interface PolicyVerdict

What a policy decides for one tool, and which rule decided it.

Attributes
toolstring

The tool's dotted name.

verdict"allow" | "deny" | "approval"
matchedPatternstring | null
layerstring | null

account or user (null when nothing matched).

rawJsonObject

The response JSON exactly as the API sent it.

rulestring | Rule | null

The rule that decided, whole: a pattern or a Rule (null when nothing matched).

Rule

class Rule

A policy rule that says more than a pattern: tool is the pattern, when a CEL expression the call must satisfy for the rule to apply, over args (the call's final arguments, bound ones included) and user (id, email, name, metadata), and approver who approves it in requireApproval: a Human (new Human() for the user the call is made for) or an email; it's emailed the approval, and only it may answer. Without an approver the owner of what the call acts in approves, as for a pattern. A call when can't judge (an argument it lacks, values that don't compare) meets a deny or requireApproval rule and misses an allow one; numbers compare within their own type (double(args.amount) > 99.5 for an amount with a fraction). message, on a deny rule, is what the call's refusal says in your words, so the agent can tell the user what to do ("Web search comes with Pro: https://..."); {{user.*}} in it is filled in for whom the call is for.

await Policy.add([new Rule("stripe.create_refund", { when: "args.amount > 10000", approver: "finance@acme.com" })]);
Attributes
toolstring
whenstring | null
approverHuman | string | null
messagestring | null

What a deny rule's refusal says, for the user.