A deterministic pre-deployment infrastructure governance engine
Project description
ObsidianWall Verdict
Pre-deployment infrastructure governance. Evaluate Terraform plans against governance policies before deployment executes — catching budget overruns, policy violations, and compliance failures before they become incidents.
What it does
Verdict sits between terraform plan and terraform apply. It takes a
Terraform plan and a policy file, evaluates the plan deterministically
against your governance policies, and produces a decision — with a full
audit trail, risk score, stakeholder notifications, and explainability
built in.
$ verdict evaluate \
--plan terraform_plan.json \
--policy policies/cost/basic_budget.yaml \
--role engineer
policy basic_budget_verdict
condition budget_check ✗ FAILED
expression (current_spend + estimated_cost) <= budget.amount
evaluated (0 + 100) <= 50 → false
risk score 75 / 100 (critical)
findings cost_analysis: 2 topology: 1
notified budget_owner (email) engineering_lead (slack)
decision DENY_WITH_OVERRIDE
override budget_owner may authorize
decision_id abc3a13b-83d5-4fad-87d8
✗ Deployment blocked by governance policy.
No AI guessing. No approximations. Every decision is deterministic, reproducible, and attributable to a human-authored policy.
Quickstart
Install
pip install obsidianwall-verdict
Write a policy
# policies/cost/budget.yaml
apiVersion: obsidianwall.io/v1
kind: Policy
metadata:
name: team_budget
version: "0.1"
owner: your-team
spec:
inputs:
- estimated_cost
- current_spend
parameters:
budget:
amount: 5000
period: monthly
flexibility: soft
conditions:
- id: budget_check
expression: "(current_spend + estimated_cost) <= budget.amount"
description: "Monthly spend must not exceed budget"
decision:
allow: ALLOW
deny: DENY_WITH_OVERRIDE
warn: ALLOW_WITH_NOTIFICATION
governance:
severity: medium
notifications:
- role: budget_owner
channel: email
- role: engineering_lead
channel: slack
override:
roles:
- budget_owner
requires_approval: false
Evaluate a plan
# Generate your Terraform plan
terraform plan -out=tfplan
terraform show -json tfplan > terraform_plan.json
# Run governance evaluation
verdict evaluate \
--plan terraform_plan.json \
--policy policies/cost/budget.yaml \
--role engineer
Verdict returns exit code 0 on ALLOW and non-zero on DENY,
blocking CI/CD pipelines automatically.
Commands
verdict evaluate
Evaluate a Terraform plan against a governance policy.
verdict evaluate \
--plan terraform_plan.json \
--policy policies/cost/budget.yaml \
--role engineer \
--current-spend 30.0
| Flag | Description |
|---|---|
--plan |
Path to Terraform plan JSON |
--policy |
Path to policy YAML file |
--role |
Role of the user triggering the deployment |
--current-spend |
Spend already incurred this month (default: 0.0) |
--pricing |
table (default, offline) or live (Azure Retail API) |
--region |
Cloud region for live pricing (default: eastus) |
--output |
Path to write the audit artifact (default: output/result.json) |
verdict validate
Check that a policy file is valid before using it in an evaluation. Useful when writing new policies — catches schema errors immediately without needing a plan file.
verdict validate --policy policies/cost/budget.yaml
Returns a JSON object with status: valid or status: invalid and
the error if the policy fails validation.
verdict test
Assert that a Terraform plan produces a specific governance decision. This is the policy regression testing command — use it to verify your policies behave correctly and catch regressions when policies change.
verdict test \
--plan terraform_plan.json \
--policy policies/cost/budget.yaml \
--expect DENY_WITH_OVERRIDE
✅ Test passed
Policy: policies/cost/budget.yaml
Plan: terraform_plan.json
Expected: DENY_WITH_OVERRIDE
Actual: DENY_WITH_OVERRIDE
If the decision does not match, Verdict tells you what failed and why:
✗ Test failed
Policy: policies/cost/budget.yaml
Plan: terraform_plan.json
Expected: DENY
Actual: DENY_WITH_OVERRIDE
Risk score: 75/100
Severity: critical
Failed conditions:
✗ budget_check
| Flag | Description |
|---|---|
--plan |
Path to Terraform plan JSON |
--policy |
Path to policy YAML file |
--expect |
Expected decision (see governance decisions table) |
--role |
Role of the user (default: engineer) |
--verbose |
Show full evaluation output on failure |
Exit codes: 0 pass, 1 fail, 2 evaluation error.
verdict audit
Review governance decisions recorded over time. Verdict stores a local
history of every evaluation when telemetry is enabled, and verdict audit
turns that history into a governance report.
Enable telemetry first:
export OW_TELEMETRY_ENABLED=true
Run the audit:
verdict audit
────────────────────────────────────────────────────────────────────────
ObsidianWall Verdict — Governance Audit
────────────────────────────────────────────────────────────────────────
Total evaluations: 12
Allowed: 4
Denied: 8 (66.7%)
────────────────────────────────────────────────────────────────────────
Domain Risk Scores (average across recorded decisions)
────────────────────────────────────────────────────────────────────────
Cost [██████░░░░░░] 50.0/100 medium
Network / Topology [███░░░░░░░░░] 25.0/100 low
Architecture [░░░░░░░░░░░░] 0.0/100 informational
Utilization [░░░░░░░░░░░░] 0.0/100 informational
────────────────────────────────────────────────────────────────────────
Why Decisions Were Made
────────────────────────────────────────────────────────────────────────
Failed conditions (why deployments were DENIED)
Condition Failures Rate
──────────────────────────────────────── ──────── ──────
budget_check 8 66.7%
────────────────────────────────────────────────────────────────────────
Policy Effectiveness
────────────────────────────────────────────────────────────────────────
Policy Evals Denied Overrides Override% Deny%
basic_budget_verdict 12 8 2 25.0% 66.7%
Add --insights to get a governance interpretation:
verdict audit --insights
This adds two sections at the end — Governance Insights (what patterns the data shows) and Recommendations (what to do about them):
────────────────────────────────────────────────────────────────────────
Governance Insights
────────────────────────────────────────────────────────────────────────
⚠ 'basic_budget_verdict' has a 25.0% override rate.
Policy may not reflect deployment reality.
ℹ Cost is the highest risk domain (avg score: 50/100).
ℹ 'budget_check' is the most frequently failed condition
(8 failures, 66.7% of evaluations).
────────────────────────────────────────────────────────────────────────
Recommendations
────────────────────────────────────────────────────────────────────────
1. Review 'basic_budget_verdict' threshold — consider whether
the policy limit reflects realistic deployment patterns.
2. Review Cost governance policies.
This domain is driving the most aggregate risk.
3. Review 'budget_check' condition — it is responsible for
66.7% of all denials.
| Flag | Description |
|---|---|
--insights |
Include governance insights and recommendations |
--policy |
Filter to a specific policy name |
--limit |
Number of recent decisions to show (default: 50) |
--format |
table (default) or json |
Decision history is stored locally at ~/.obsidianwall/decisions.db.
Nothing leaves your machine. Remote telemetry is not implemented yet —
that is planned for v0.5.0 with explicit opt-in.
GitHub Actions
Add Verdict as a governance gate in your CI/CD pipeline:
# .github/workflows/governance.yml
name: Infrastructure Governance
on:
pull_request:
paths: ["**.tf", "**.tfvars"]
jobs:
governance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
- name: Generate Terraform plan
run: |
terraform init
terraform plan -out=tfplan
terraform show -json tfplan > terraform_plan.json
- name: ObsidianWall Verdict
id: verdict
uses: obsidianwall/obsidianwall-verdict@main
with:
plan: terraform_plan.json
policy: policies/cost/budget.yaml
role: engineer
fail_on_deny: "true"
- name: Governance outcome
if: always()
run: |
echo "Decision: ${{ steps.verdict.outputs.decision }}"
echo "Risk score: ${{ steps.verdict.outputs.risk_score }}/100"
echo "Severity: ${{ steps.verdict.outputs.effective_severity }}"
Action outputs
| Output | Description |
|---|---|
decision |
ALLOW / ALLOW_WITH_NOTIFICATION / ALLOW_WITH_APPROVAL_REQUIRED / DENY_WITH_OVERRIDE / DENY |
conditions_passed |
true or false |
risk_score |
Integer 0–100 |
effective_severity |
informational / low / medium / high / critical |
decision_id |
UUID for audit trail correlation |
How it works
Terraform plan
↓
Translation Layer Parses plan, estimates cost
↓
Policy loader Loads and validates the policy YAML
↓
Runtime normalizer Flattens policy parameters into evaluation context
↓
Condition evaluator Evaluates each condition deterministically
↓
Analyzer framework Cost, topology, architecture, utilization analysis
↓
Risk scorer Aggregates findings into risk score (0–100)
↓
Decision resolver 5-level governance decision with override routing
↓
Explainability Reasoning chain, trace graph, remediation steps
↓
Notification manifest Stakeholder routing — never dispatched automatically
↓
Audit artifact Immutable JSON record of the complete evaluation
Every stage is deterministic. Analyzers are advisory — they inform the risk score but never override the condition evaluation. The condition evaluation alone determines the governance decision.
Enforcement modes
| Mode | How | What it blocks |
|---|---|---|
| CI/CD pipeline | GitHub Actions with fail_on_deny: true |
terraform apply never runs on DENY |
| IAM access controls | Azure Entra ID / AWS IAM restricts engineer credentials to read-only | Direct deployment from local machines impossible |
| Standalone manual | Run verdict evaluate before terraform apply |
Governance guidance, audit trail, budget owner notification |
For hard technical enforcement, integrate Verdict into your CI/CD pipeline and restrict cloud credentials so only the pipeline's service principal can apply infrastructure. Engineers with read-only credentials cannot deploy directly even if they skip Verdict locally.
Governance decisions
| Decision | Meaning |
|---|---|
ALLOW |
All conditions passed. Deployment authorized. |
ALLOW_WITH_NOTIFICATION |
Conditions passed but stakeholders are notified. |
ALLOW_WITH_APPROVAL_REQUIRED |
Conditions passed but formal approval is required. |
DENY_WITH_OVERRIDE |
Conditions failed. An authorized role may override. |
DENY |
Conditions failed. No override permitted. Hard block. |
Policy DSL
Policies are YAML files that define governance constraints for an infrastructure change. The schema is versioned and validated on load.
Policy structure
apiVersion: obsidianwall.io/v1 Protocol version
kind: Policy Always Policy for now
metadata:
name: string Policy identifier
version: string Semver string
owner: string Responsible team
description: string Optional description
spec:
inputs: list[string] Runtime context keys required
parameters: dict Policy parameters (flattened at runtime)
conditions: list[Condition] Evaluated deterministically
decision: Decision allow / deny / warn mappings
governance: GovernanceConfig Severity, notifications, approvals
override: Override Roles and approval requirements
actions: list[Action] notify / log actions
Governance domains
Policies declare which governance domain they enforce. Verdict validates conditions against the declared domain and rejects mismatches.
| Domain | What it governs |
|---|---|
cost |
Budget spend enforcement |
security |
Security posture — open ingress, public storage, encryption |
compliance |
Tagging, naming, regulatory requirements |
resource_limits |
Instance counts, GPU limits, sizing |
network |
Network topology, segmentation, public exposure |
identity |
IAM, MFA, privileged access |
data_governance |
PII handling, encryption, data residency |
resilience |
Availability, DR, replica counts |
ai_governance |
AI system controls, model provenance, GPU workloads |
composite |
Coordinates multiple domains in one policy |
Composite policies
A composite policy governs multiple domains simultaneously and produces a single governance decision. Every domain used must be declared explicitly — Verdict enforces this and rejects any condition that references an undeclared domain.
spec:
policy_type: composite
governance_domains:
- cost
- security
- compliance
Condition expressions
conditions:
- id: budget_check
expression: "(current_spend + estimated_cost) <= budget.amount"
description: "Monthly spend must not exceed budget"
- id: no_open_ingress
expression: "open_ingress_rules <= security.max_open_ingress_rules"
description: "No unrestricted inbound rules permitted"
Supported operators: <=, >=, <, >, ==
Supported arithmetic: +
Context resolution: dot-notation for nested parameters (budget.amount)
Audit artifact
Every evaluation produces a complete audit artifact:
{
"decision_id": "abc3a13b-83d5-4fad-87d8-bbe77e4b8075",
"timestamp": "2026-05-20T04:05:28Z",
"policy": "basic_budget_verdict",
"decision": "DENY_WITH_OVERRIDE",
"override_possible": true,
"override_required": false,
"conditions_passed": false,
"effective_severity": "critical",
"risk_summary": {
"overall_risk_score": 75,
"risk_severity": "critical",
"highest_risk_analyzer": "cost_analysis",
"total_findings": 3
},
"trace": [...],
"explanation": {
"governance_reasoning": {...},
"policy_reasoning": {...},
"trace_graph": {...}
},
"notification_manifest": {...}
}
The artifact is written to output/result.json and printed to stdout.
It is suitable for storage in an audit log, S3 bucket, or compliance system.
Doctrine
AI may advise.
AI may explain.
AI may optimize.
AI may correlate.
AI may recommend.
AI may NOT authoritatively govern.
Every governance decision in ObsidianWall Verdict is produced by deterministic evaluation of human-authored policy conditions — never by a probabilistic model.
Decisions are reproducible, explainable, and attributable to a named policy and a named human who wrote it.
This is not an anti-AI position. The analyzer framework, recommendation engine, and explainability pipeline all use intelligence to inform the governance process. The boundary is authority: intelligence informs, policy governs.
Architecture
Verdict is the first executable of the ObsidianWall programmable assurance platform.
┌─────────────────────────────────────────────────────┐
│ Open Governance Core (this repo — open source) │
│ │
│ engine/ deterministic evaluation pipeline │
│ schemas/ policy DSL and typed contracts │
│ context/ Translation Layer (plan parsing) │
│ telemetry/ local decision history (SQLite) │
│ audit/ structured audit logging │
│ cli/ command-line interface │
└─────────────────────────────────────────────────────┘
↓ telemetry (opt-in, local SQLite)
┌─────────────────────────────────────────────────────┐
│ Intelligence Layer (future — private) │
│ │
│ Derived optimization intelligence │
│ Workload pattern recognition │
│ Pricing behavior intelligence │
│ Predictive governance scoring │
└─────────────────────────────────────────────────────┘
↓ enterprise workflows
┌─────────────────────────────────────────────────────┐
│ Platform Layer (future — paid) │
│ │
│ Hosted policy management │
│ Approval workflow persistence │
│ Governance dashboards │
│ RBAC, SSO, multi-tenant │
│ Compliance exports │
└─────────────────────────────────────────────────────┘
Telemetry
Telemetry is opt-in and disabled by default. When enabled,
Verdict records governance decisions to a local SQLite database at
~/.obsidianwall/decisions.db. Nothing is sent anywhere remotely.
export OW_TELEMETRY_ENABLED=true
What is stored:
- Decision outcomes, risk scores, policy names
- Which conditions passed and which failed
- Override and approval events
What is never stored:
- Plan contents or resource configurations
- Cost amounts or budget values
- Resource names or identifiers
- Organization or team identifiers
Telemetry powers verdict audit. Without it, verdict audit has no
data to read. Remote telemetry with explicit opt-in is planned for v0.5.0.
Development
Requirements: Python 3.11+, Git
git clone https://github.com/obsidianwall/obsidianwall-verdict
cd obsidianwall-verdict
pip install -e ".[dev]"
# Run the full test suite
pytest tests/ -v
# Run a sample evaluation
verdict evaluate \
--plan samples/terraform_plan.json \
--policy policies/cost/basic_budget.yaml \
--role engineer
Test suite: 101 tests — unit, integration, and pipeline.
pytest tests/unit/ # unit tests
pytest tests/integration/ # integration tests
pytest tests/ # full suite
Policy examples are in policies/ organized by governance domain:
policies/
cost/ budget enforcement
security/ security posture
compliance/ tagging and regulatory
network/ topology and exposure
identity/ IAM and Zero Trust
ai_governance/ AI system controls
composite/ multi-domain coordination
License
Verdict is released under the Apache License 2.0.
Free to use, modify, and distribute for any purpose — commercial or non-commercial. Attribution required.
Built on ObsidianWall — the programmable assurance platform. obsidianwall.com
Built by
Aisha I. — obsidianwall.com
"Organizations that design for programmable assurance now will not need to retrofit later."
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file obsidianwall_verdict-0.4.3.tar.gz.
File metadata
- Download URL: obsidianwall_verdict-0.4.3.tar.gz
- Upload date:
- Size: 105.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
31a753773dee01a7a28ef1358dd202676b3d311adec34b24d510c2b743ad8b89
|
|
| MD5 |
280054e138dc1d2b0b253ab7623d4df0
|
|
| BLAKE2b-256 |
cd81984c66662c30ccc8c5666dff4a166efffdfcebc46377fc951f4bcffd5e18
|
File details
Details for the file obsidianwall_verdict-0.4.3-py3-none-any.whl.
File metadata
- Download URL: obsidianwall_verdict-0.4.3-py3-none-any.whl
- Upload date:
- Size: 124.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3559e3fdf824f595cd8c6efa7022ed15f7991fc32c4bca356d2dce02b3b9e3b7
|
|
| MD5 |
865514a9a202efd46c49741419fc0a23
|
|
| BLAKE2b-256 |
a50a96540fc4ee459c5e92d781a7b1d23066c30c487dc0d0429179da72c5c4b1
|