Policy Engine
Rule-based governance with 12 field operators, scope globs, and time windows.
The policy engine evaluates every governed LLM call against a set of rules before the call is forwarded to the provider. Rules are defined in YAML or JSON and support field matching, scope globs, time windows, and priority ordering.
Enforcement Levels
Each rule specifies an enforcement level that determines what happens when the rule matches:
| Enforcement | Effect |
|---|---|
| hard | Deny the request. Throws PolicyDeniedError. |
| soft | Allow the request with warnings. The governance receipt includes the matched rule and reason. |
PolicyResult
Every policy evaluation returns a structured result:
interface PolicyResult {
decision: "allow" | "deny";
hasWarnings: boolean;
matched: RuleMatch[];
hardViolations: RuleMatch[];
softViolations: RuleMatch[];
reasons: string[];
evaluatedAt: string;
}Rule Structure
A policy rule specifies conditions, scope, enforcement, and optional time windows:
# usertrust.config.json — operator-declared host scope (required for the rule below)
# { "budget": 100000, "scope": ["production/api"] }
# .usertrust/policies/default.yml
rules:
- name: block-expensive-models
description: Deny requests to opus-class models in production
effect: deny
enforcement: hard
priority: 10
scopePatterns: ["production/*"]
conditions:
- field: model
operator: contains
value: "opus"
timeWindows:
- daysOfWeek: [1, 2, 3, 4, 5] # Mon-Fri
startHour: 9
endHour: 17Operators
The policy engine supports 12 field operators for matching conditions. Each operator compares a field value (resolved via dot-notation) against the rule's expected value.
| Operator | Description |
|---|---|
exists | Field is present |
not_exists | Field is absent |
eq | Equal (strict) |
neq | Not equal |
gt | Greater than |
gte | Greater than or equal |
lt | Less than |
lte | Less than or equal |
in | Value is in array |
not_in | Value is not in array |
contains | String contains substring |
regex | Matches regular expression |
See Policy Operators for detailed usage and examples of each operator.
Scope Matching
scopePatterns match the operator-declared scope on the governor or config — never request content. Set it the same way you set budget or parentUserId:
await trust(client, { scope: ["production/api"] });
// or in usertrust.config.json: { "scope": ["production/api"] }The patterns themselves are minimatch globs:
scopePatterns: ["production/*"] # all production sub-scopes
scopePatterns: ["development/**"] # development and all nested scopes
scopePatterns: ["*/billing"] # billing scope in any environmentIf the operator sets no scope, a rule with scopePatterns never matches — adding a pattern does not "narrow a live rule"; it stays inert until the host declares a matching scope. A request body / params.params.scope cannot volunteer one.
If a rule has no scopePatterns field, it matches all calls.
Time Windows
Rules can be restricted to specific days and hours:
timeWindows:
- daysOfWeek: [1, 2, 3, 4, 5] # 0=Sunday, 6=Saturday
startHour: 9 # 24-hour format
endHour: 17A rule with a time window only matches during the specified period. Outside the window, the rule is skipped entirely.
Priority
Rules are evaluated in priority order. Lower numbers are evaluated first (higher priority):
| Priority | Convention |
|---|---|
| 1-10 | Critical safety rules |
| 10-50 | Cost and budget rules |
| 50-100 | Operational rules |
| 100 | Default priority |
If multiple rules match, all hard violations result in denial. Soft violations accumulate as warnings.
Default Rules
usertrust ships with three built-in rules:
- block-budget-overshoot -- Deny when
budget_remaining_afterlt0 (hard) - block-budget-exhausted -- Deny when
budget_remaininglte0 (hard) - warn-high-cost -- Warn when
estimated_costgt1000 (soft)
Further Reading
- Policy Rules -- Full rule definition reference
- Policy Operators -- Detailed operator documentation with examples
- PII Detection -- Built-in PII detection and redaction