Policy reference (CEL)
flowifai adjudicates every tool call your broker forwards against your org’s policy rules. This page is the exact contract: what a rule is, how matching works, which effect wins, and how the engine fails.
What a rule is
A policy rule has:
| Field | Meaning |
|---|---|
| Tool pattern | The exact tool name to match, or * for any tool. |
| Argument predicate | Optional CEL expression over the call’s arguments. Empty means “always matches”. |
| Effect | allow, needs_approval, or deny. |
| Enabled | Disabled rules never match. |
| Approval requirement | For needs_approval rules only: how many approvals (n-of-m), which approver groups are eligible, and whether approvals must span at least two groups. |
Rules are org-level. Tool names come from the schemas your registered MCP servers expose — the console’s policy view shows which registered server provides each tool, so you can scope a rule to a server’s tools by naming them exactly.
Matching
A rule matches a call when both hold:
- its tool pattern matches the called tool (exact name, or
*), and - its argument predicate — if present — evaluates to
true.
Predicates are CEL expressions evaluated with two variables:
args— the call’s arguments as a map (map<string, dyn>), decoded from the tool call’s JSON arguments object;tool— the called tool’s name as a string.
Examples:
has(args.query) && type(args.query)==string &&
(args.query.contains("DROP")||args.query.contains("DELETE")||args.query.contains("TRUNCATE"))
has(args.amount) && type(args.amount)==double && args.amount > 0.0
has(args.path) && has(args.recursive) && args.recursive == true
Predicates are compiled and validated when you save the rule; a rule with a predicate that does not compile is rejected.
Most restrictive wins, default deny
All enabled rules are evaluated against the call. Among the rules that match, the most restrictive effect wins:
deny > needs_approval > allow
If no rule matches, the decision is deny. There is no implicit allow anywhere:
you must write an allow rule for the tools you want to pass ungated.
When the winning decision is needs_approval and several needs_approval rules
matched, the approval requirement is the strictest merge across them: the maximum
approvals-required count, the union of eligible approver groups, and cross-group
required if any matching rule requires it. The matched rules are surfaced to approvers
as the “why gated” provenance on the ticket.
Fail-closed behavior
The engine treats every abnormal condition as a denial:
- Unparseable arguments → deny.
- Predicate evaluation error (for example, a predicate that touches a wrong-typed field) → the whole call is denied, even if another rule would have allowed it.
- Unknown effect value → treated as deny.
Because an erroring predicate denies the whole call, write broad (*-pattern)
predicates type-guarded so they can only ever evaluate to true or false:
has(args.command) && type(args.command)==string && args.command.contains("deploy")
The has(...) and type(...) guards ensure the predicate never errors regardless of
the argument shape a tool sends.
Policy templates
The console ships a curated catalog of commit-point templates (destructive SQL,
payment/charge, outbound send, recursive filesystem delete, deploy/migrate/apply,
git force-push). Every template uses tool pattern * with a type-guarded predicate
and effect needs_approval. Templates key on guessed argument field names — real
MCP tools use arbitrary schemas — so treat an applied template as a starting point and
verify it matches your tool’s actual fields (for example, your DB tool may call the
query field sql or statement instead of query).
Schema drift and stale rules
Enrollment registers your MCP server’s tool schema. If the live schema drifts from the
registered one, the broker blocks all calls to that server until you re-run the
app-generated flowifai connect command. Additionally, argument-level rules that
reference a changed schema are disabled (fail closed) until re-confirmed — a
renamed or re-typed field can never quietly turn a gate into a pass.
Who can edit policy
Policy create/delete requires a policy-admin user, which is a separate capability from approval eligibility. A single approver therefore cannot rewrite policy to bypass n-of-m or separation-of-duty requirements.
Managing rules
Manage rules in the console under Policies, or from the CLI:
flowifai policy list
flowifai policy add --name "gate deploys" --tool "*" \
--predicate 'has(args.command) && type(args.command)==string && args.command.contains("deploy")' \
--effect needs_approval
flowifai policy delete <id>
Rule changes are recorded in the audit log like every other security-relevant event.