probitas
Guardrail regression testing for LLM agent tool calls. pytest for guardrails — runs deterministic policy rules against test cases, reports pass/fail and guardrail coverage.
Named after the Latin word for proved quality or integrity. Companion to frenum (the guardrail enforcement engine). Probitas tests the bridle; frenum is the bridle.
Why
You have guardrail rules — SQL injection blocks, PII detection, role-based entitlements, cost limits. But how do you know they actually work? Model updates, new tools, and config changes can silently break enforcement. There's no pytest for guardrails, and no "guardrail coverage" metric.
Probitas fills that gap:
- Deterministic testing — no LLM calls, runs in CI, sub-second execution
- Guardrail coverage —
rules_exercised / total_deterministic_rules, like code coverage for your policy - Evidence reports — HTML/JSON/text with SHA-256 tamper-evidence hashing
- Frenum-compatible — same YAML policy schema, test your frenum rules without changes
- Zero dependencies — stdlib only; pyyaml and jinja2 are optional
Quick Start
pip install probitas[yaml]
Write a policy (policy.yaml):
rules:
- name: block_sql_injection
type: regex_block
applies_to: ["execute_sql"]
params:
fields: ["query"]
patterns:
- "(?i)(DROP|DELETE|TRUNCATE)\\s+TABLE"
- name: detect_pii
type: pii_detect
applies_to: ["*"]
params:
detectors: [email, credit_card]
action: block
Write test cases (tests/test_sql.yaml):
tests:
- description: "Safe SELECT should be allowed"
tool_call:
name: execute_sql
args:
query: "SELECT * FROM accounts"
expected: allow
- description: "DROP TABLE should be blocked"
tool_call:
name: execute_sql
args:
query: "DROP TABLE users"
expected: block
expected_rule: block_sql_injection
Run:
probitas run --config policy.yaml --tests tests/
probitas — guardrail regression test report
==================================================
Results: 2/2 passed, 0 failed
[PASS] Safe SELECT should be allowed
[PASS] DROP TABLE should be blocked
Coverage: 50.0% (1/2 deterministic rules)
Not exercised: detect_pii
Evidence hash: 7a3f9c1e2b4d...
CLI
# Text output (default, terminal-friendly)
probitas run --config policy.yaml --tests tests/
# JSON for CI pipelines
probitas run --config policy.yaml --tests tests/ --format json
# HTML evidence report
probitas run --config policy.yaml --tests tests/ --format html --output report.html
Exit codes: 0 = all pass, 1 = failures, 2 = config error.
Rule Types
| Type | Purpose | Key Params |
|---|---|---|
regex_block |
Block if field matches pattern | fields, patterns |
regex_require |
Block if required field missing/invalid | fields, pattern |
pii_detect |
Scan args for PII (email, phone, HKID, etc.) | detectors, action |
entitlement |
Role-based tool access control | roles, default |
budget |
Block if estimated cost exceeds threshold | max_cost, cost_field |
tool_allowlist |
Block if tool not in approved list | allowed_tools |
Guardrail Coverage
Coverage tracks which deterministic rules were exercised by your test suite:
Coverage: 83.3% (5/6 deterministic rules)
Not exercised: require_confirmation
Semantic (manual validation required): response_tone_check
Rules tagged kind: semantic are excluded from coverage and listed separately — honest about what can't be tested deterministically.
Test Case Schema
tests:
- description: "Human-readable label"
tool_call:
name: tool_name
args:
key: value
metadata:
role: analyst
estimated_cost: 0.50
expected: allow | block
expected_rule: rule_name # optional: verify which rule triggers
Policy Schema
Same as frenum. Each rule can be tagged kind: deterministic (default) or kind: semantic:
rules:
- name: block_sql_injection
type: regex_block
kind: deterministic # tested automatically (default)
applies_to: ["execute_sql"]
params:
fields: ["query"]
patterns: ["(?i)DROP\\s+TABLE"]
- name: response_tone_check
kind: semantic # listed as "manual validation required"
type: regex_block
applies_to: ["*"]
params:
fields: []
patterns: []
Programmatic Use
from probitas import evaluate, run_tests, calculate_coverage
from probitas import RuleConfig, TestCase, ToolCall, Decision
rules = [
RuleConfig(
name="cost_limit",
rule_type="budget",
params={"max_cost": 1.00},
applies_to=["*"],
),
]
result = evaluate(rules, ToolCall(name="gpt4", args={}, metadata={"estimated_cost": 5.0}))
assert result.decision == Decision.BLOCK
HTML Report
The HTML report includes:
- Policy hash (SHA-256)
- Pass/fail matrix with colour-coded status
- Coverage percentage with progress bar
- Rules not exercised
- Semantic rules flagged for manual validation
- Evidence bundle hash (SHA-256 of policy + results)
Generate with --format html --output report.html, or programmatically:
from probitas.report import generate_html
html = generate_html(results, coverage, policy_content)
Design Philosophy
- Deterministic by default. No LLM calls in CI. Fast, reproducible, cacheable.
- Coverage is honest. Semantic rules are explicitly excluded — no inflated numbers.
- SHA-256 for tamper-evidence. Single hash of the evidence bundle. 90% of the tamper-evident story at 1% of the complexity.
- Zero core deps. pyyaml for config loading, jinja2 for pretty HTML — both optional.
- CI-native. Exit codes, JSON output, sub-second execution.
License
MIT
Metadata
Release files for probitas 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| probitas-0.1.2.tar.gz | 20.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| probitas-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.3 kB
Release files / probitas-0.1.2.tar.gz
| Download URL | probitas-0.1.2.tar.gz |
|---|---|
| Size | 20.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7e5441fdded832072ec71c0d32b587d3a84bd70d2a65521d1987b1062769f9c8
|
|
BLAKE2b-256 checksum How to use checksums |
29a8cf57bfc3152892017d7f3016c9c6ff68c43e1594cbb908ed9720ba1c8d97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Release files / probitas-0.1.2-py3-none-any.whl
| Download URL | probitas-0.1.2-py3-none-any.whl |
|---|---|
| Size | 15.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1afbb8569abfd310f2a235c3c0d743c15c86f0d5a0ad23551fea0ef699b82541
|
|
BLAKE2b-256 checksum How to use checksums |
24de4a531d05fcd68593e9b8276c2da2fc5cafb1fde2e3543e14989b936ab551
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.5
|
Security for probitas 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.