On this page
- The rule was visible. The exception lived in a hidden script.
- What makes a visual policy rule trustworthy?
- Rules, validations, decisions and policies aren't the same thing
- Facts need a source, a type and a meaning
- How should a policy rule handle missing data?
- Outcomes beyond allow and deny
- Conditions become runtime work
- When several rules apply, the combination is policy too
- Does every path actually cross the rule?
- Test semantics, not just examples
- Version everything the decision depends on
- Common anti-patterns
- Ten design rules for visual policy
- The result: rules people can read and trust
- Frequently asked questions
- How Frozion keeps the visible rule and the running rule the same
- Sources
A visual policy rule is trustworthy only if what reviewers see is exactly what runs. That means typed facts with known sources, explicit handling of missing data, outcomes beyond allow and deny, conditions bound to the steps that enforce them, visible precedence between rules, and tests and versions that cover all of it.
The rule was visible. The exception lived in a hidden script.
A distributor runs an AI-assisted supplier refund process. The approved policy sits in a visual builder, clear enough for anyone to read: refunds above $25,000 need a second finance approver; so does any change to the beneficiary bank account; and both approvals must stay bound to the exact refund proposal.
On screen it reads: IF amount > 25,000 OR beneficiaryChanged THEN require second finance approval.
beneficiaryChanged is calculated by a custom expression behind a property panel. It was written when the refund record had a field called beneficiaryAccountId. A later schema version moved it to beneficiary.account.id. The diagram didn't change. The script still read the old field, found nothing, and returned false instead of an error.
An $18,000 refund to a newly supplied account skipped the second approval. The first approver checked the amount, the policy service returned a valid decision, and the payment API confirmed the transfer.
Nothing threw an error. The rule still showed the control, the script ran, the approver was eligible, the payload matched its schema. The failure wasn't using expressions; policy always needs them. It was letting one expression become a hidden, weakly typed, separately changing source of authority.
What makes a visual policy rule trustworthy?
A visual rule is trustworthy when the diagram is the policy rather than a picture of it: typed facts, defined handling of missing data, explicit outcomes and conditions, visible precedence, tests and versions, all in one definition that the runtime executes.
A readable canvas is useful, but readability isn't governance. Besides "does this evaluate to true?", a governed rule must answer:
- What exactly is being evaluated, and for whom?
- Which facts are authoritative, current and allowed for this decision?
- What happens when a fact is missing, stale or conflicting?
- Which outcome results, and which conditions come with it?
- Which step enforces it, and what invalidates it later?
- What evidence proves it constrained the real outcome?
Rules, validations, decisions and policies aren't the same thing
"Rule" gets used for at least six jobs, and they don't carry the same authority:
| Artifact | Question it answers | Typical outcome |
|---|---|---|
| Structural validation | Is the payload shaped correctly? | Valid / invalid |
| Business validation | Does it satisfy business invariants? | Valid / invalid for the domain |
| Decision logic | Which route or result follows from the facts? | Route A / route B / review |
| Authorization policy | May this actor do this, to this record, for this purpose? | Permit / deny / not applicable / indeterminate |
| Human judgment | What does an eligible reviewer decide about this exact proposal? | Approve / deny / ask for more |
| Model instruction | How should a model attempt the task? | Guidance, not enforcement |
Keep these visibly distinct. Otherwise a schema check gets mistaken for authorization, a routing rule for an approval, or a prompt for a control.
Facts need a source, a type and a meaning
A policy evaluates a whole proposition, not a user or a record in isolation: who started it, who's acting now, which software is running, the tenant and legal entity, the record and its revision, the action and purpose, the exact proposal, the current state, and the time and risk conditions. A role like "Finance Manager" is one input, not the decision.
Every fact in that proposition needs a contract. amount = 18000 means little until you know whether it's the proposed, approved, gross, net or estimated amount. beneficiaryChanged = false isn't defensible unless you can say which two records were compared, with which IDs and schema version, and whether either value was missing.
| Fact type | Examples | What reviewers need to see |
|---|---|---|
| Actor | Role, department, delegated scope | Who issued it, when it expires, which tenant |
| Workload | Service identity, deployment | Which software is running, and is it allowed here |
| Record | Owner, classification, revision | Which exact object and version |
| Action | Refund, disclose, approve | Which fields may change |
| Purpose | Support, payroll, fraud review | Why, and is that purpose allowed |
| Environment | Time, location, sign-in strength | Which current conditions matter |
| Proposal | Amount, beneficiary, terms | What exactly will be enforced later |
A visual builder should show where each fact comes from rather than presenting anonymous variables. Facts drawn from history (counts, totals, status over time) need the same contract, including how fresh they must be.
How should a policy rule handle missing data?
Missing, stale, conflicting or unauthorized facts are policy outcomes, not programming inconveniences. Each needs a declared response, because silently converting them to false, zero or a default is how controls disappear.
Scripts typically coerce a missing value into false, zero, an empty string, a default option or a swallowed exception. Any of those can quietly widen authority, exactly as beneficiaryChanged did. A governed rule names the states:
- available, missing, stale, not authorized, conflicting, invalid, source unavailable;
and declares what each leads to: deny, refresh the data, request more evidence, send to a person, mark the decision indeterminate, or stop before any change. Nobody should discover months later that null == false is why a financial control vanished.
Outcomes beyond allow and deny
Authorization standards already distinguish Permit, Deny, Not Applicable and Indeterminate. Business policy often needs more: review required, step-up sign-in required, reapproval required, wait for fresh data, permit with conditions, block.
Each of these is a state change with consequences, not a hint to the caller. "Review required" moves the case to a person. "Permit with conditions" isn't Permit plus optional notes; the conditions are part of what was permitted.
Conditions become runtime work
A rule may permit an action only if the runtime also redacts fields before the model sees them, uses an approved region, caps an amount, requires a second approver, asks for step-up sign-in, keeps a receipt, re-checks before the write, or retains evidence. Each of those must be tied to the specific step that performs it and produces proof that it did.
That's the point where many visual builders stop: they show the decision but not where it's enforced. A valid decision is not proof the policy was enforced. For how enforcement points work across an agent workflow, see runtime policy enforcement.
When several rules apply, the combination is policy too
Real decisions combine rules from enterprise governance, tenant settings, legal entity, product line, jurisdiction, data class, process and exceptions. How they combine has to be explicit: deny overrides, permit overrides, first applicable, explicit priority, merged conditions, or a person decides on conflict.
None is always right. Deny-overrides suits sensitive disclosure. Permit-overrides is only safe when the permitting rule is an authorized exception. First-applicable is dangerous if someone can reorder rows by dragging them. A visual builder should show not just the rules, but how several applicable rules become one decision.
Scope needs the same visibility. Reviewers should be able to see which workflows use a rule, which tenants or business branches inherit it (see taxonomy-scoped authorization), which exceptions narrow or widen it, and which paused cases are still bound to an older version.
Does every path actually cross the rule?
Only a coverage check answers that. A rule can be well written and well tested while one fallback branch, retry path or sub-workflow reaches the action without it.
Reviewing rules in isolation misses the path problem. The stronger question is structural: does every route to this consequential action pass the required check and produce the required evidence? A coverage view can expose one branch that reaches payment without a second approval, one fallback model route that skips the residency rule, one sub-workflow that drops the tenant, one retry that reuses an expired decision, or one output channel with no disclosure rule.
Test semantics, not just examples
Testing belongs in the policy definition, not in a separate spreadsheet.
| Test | Question |
|---|---|
| Positive | Does an eligible actor get the intended result? |
| Negative | Is an ineligible actor or record denied? |
| Boundary | What happens at exactly $25,000, or one second before expiry? |
| Missing fact | Does missing data fail safely rather than default open? |
| Stale or revoked | Is an old role, approval or record revision rejected? |
| Conflict | Which rule wins, and are all conditions kept? |
| Condition | Does every enforcement step carry out and record its condition? |
| Path coverage | Can any route reach the action without the check? |
| Regression | Did a schema, mapping or taxonomy change alter earlier results? |
Then simulate. Run the proposed version against synthetic and recorded cases, with changed facts, scopes and precedence, and compare outcomes, with no live side effects. A simulated result never authorizes a real action.
Version everything the decision depends on
A policy version number is incomplete if the decision can change because something else changed underneath it. A rule depends on fact schemas, sources, taxonomy versions, mappings, shared functions, combination rules, enforcement bindings and tests. The schema rename in the opening story was exactly this kind of unversioned dependency.
Paused work shouldn't automatically adopt the newest rule either. Each workflow should declare whether a waiting case stays on the version it started under, rechecks current authority and record state, needs reapproval, migrates, or stops. Decide that up front rather than letting it happen by accident.
Common anti-patterns
| Anti-pattern | Why it fails | What to do instead |
|---|---|---|
| Visual shell, scripted core | Readable labels hide the conditions that matter | Typed expressions and declared functions |
| Boolean-only policy | Loses review, step-up, conditions and indeterminate states | Explicit, typed outcomes |
| Facts without provenance | Variables look trusted with no source or freshness | A contract for every fact |
| Missing data coerced to false | A schema change switches off a control | Named missing-data states with declared responses |
| Row order as precedence | Dragging a row changes which rule wins | Declare the combination strategy |
| Tests without enforcement | The logic passes, the path skips it | Test path coverage and enforcement |
| Latest rule on resume | Paused cases adopt rules they weren't approved under | Declare version behavior for waiting work |
| Prompt as policy | A model instruction can't enforce anything | Enforce in the runtime |
Ten design rules for visual policy
- Evaluate a complete proposition, not anonymous variables.
- Give every fact a type, source, scope, freshness and missing-data meaning.
- Keep validation, decision logic, authorization, human judgment and prompts distinct.
- Make outcomes and conditions explicit; never collapse them into a boolean.
- Tie every mandatory condition to the step that enforces it.
- Show how multiple rules combine and which one wins.
- Test boundaries, missing facts, conflicts, coverage and version changes.
- Bring custom code in as typed, versioned, tested functions.
- Version facts, mappings, taxonomies and enforcement together with the rule.
- Keep the decision, its enforcement and the confirmed outcome linked.
The result: rules people can read and trust
Visual policy isn't a replacement for precise policy semantics. It's how you make those semantics inspectable by the people accountable for them. Business owners see the intended control, security sees actor, record, action, purpose and scope, engineers see typed facts and enforcement steps, auditors trace decisions to outcomes, and the runtime executes the same published meaning.
The goal isn't to hide complexity behind friendly shapes. It's to make the complexity that matters visible, so nobody has to read a general-purpose script to find out whether a control is really on.
Frequently asked questions
What makes a visual policy builder safe for AI governance?
It's safe when the diagram is the policy, not a picture of it. Every fact needs a type and source, missing data needs a defined outcome, conditions must map to enforcement steps, and any custom code must be typed, versioned and tested like the rest of the rule.
How should a policy rule handle missing data?
Treat missing, stale, conflicting or unauthorized facts as explicit outcomes, not as false or zero. Each should lead to a declared response, such as deny, refresh, request more evidence, or send to a person, so a schema change can't silently switch off a control.
How do you test policy rules before publishing them?
Test positive, negative and boundary cases, missing and stale facts, rule conflicts, and whether every path to the action actually passes the check. Then simulate the new version against recorded cases with no live side effects and compare the outcomes.
How does this work for agents we already run?
Route the agent's tool calls through a governed runtime over MCP or HTTPS. Visual rules are then evaluated before and after every routed call, using facts from your systems and history, and each decision is recorded alongside the confirmed outcome.
How Frozion keeps the visible rule and the running rule the same
In Frozion, policies are built in the Visual Policy Builder and enforced before and after every step. Rules decide with history from the Feature Store, where features are authored in SQL or visually, with no pipeline code. Before a change ships, the dependency view shows every workflow that uses it, workflow versions are compared side by side, and the change is simulated in batch on recorded cases. Publishing needs approval, and rollback is built in. Behavior Intelligence & Replay lets reviewers inspect past cases without live side effects.
Sources
- OMG Decision Model and Notation (DMN)Human-readable, executable decision tables and expressions.
- OASIS XACML 3.0 Core SpecificationPermit, Deny, NotApplicable, Indeterminate, obligations and combining algorithms.
- NIST SP 800-162, Attribute Based Access ControlPolicy over subject, object, operation and environment attributes.
- NIST SP 800-207, Zero Trust ArchitecturePer-request decisions with no implicit trust.
- OWASP Authorization Cheat SheetDeny by default and validate permissions on every request.
- JSON Schema Draft 2020-12Typed structure for facts; structure isn't meaning.
- W3C SHACLComposable constraints with validation reports.
- NIST AI Risk Management FrameworkLifecycle governance, testing and documentation.
- W3C PROV-O: The PROV OntologyNeutral provenance for decision-to-effect evidence.
More in Governing AI agents
Identity & access 1
Tools & MCP 1
Other topics