A study in declarative infrastructure
The declarative layer, reimagined.
pravo is an open study of what OpenTofu and Terraform's declarative layer could be if it started today: state, plans, policy and language rebuilt from a clean slate, on the providers that already exist. It is built from ten years of running them.
12 minutes · no account · no cloud · Apache 2.0
package estate
import loc "pravo.dev/providers/hashicorp/local"
$local: loc.#Provider
#Service: {
name: string
port: int & >=1024 & <=65535
file: $local.#Resource.local_file & {
filename: "\(name).env"
content: "PORT=\(port)\n"
}
}
api: #Service & {name: "api", port: 8080, file: file_permission: "0600"}
web: #Service & {name: "web", port: 8081}
pravo will perform the following actions:
# api.file (local_file) will be created
+ api: file: {
...
+ file_permission: "0600"
+ filename: "api.env"
...
Plan: 2 to add, 0 to change, 0 to destroy.
Graph: planned 2 of 2.
One definition, two instances and the plan for both.
Kept, revisited, tried
The providers stay. The foundation is new.
OpenTofu and Terraform got the providers and the plan-and-apply loop right. Where ten years of operating them point elsewhere, pravo starts from a clean slate: the state, the plan, the policy and the language.
What worked
- Every OpenTofu and Terraform provider, unchanged.
- Declarative desired state, not a program that runs.
- A plan you read before anything changes.
What ten years of operating it taught
- State is one record per resource, not one file.
- A plan reaches what a change can affect and skips the rest.
- Locks are per resource, so applies on different resources run at once.
- A refresh age says when to read real state again.
- Policy is part of the tool, named by whoever runs it.
- Providers ship as OCI images, with a hash for each platform.
- The plan is a document that people, CI and agents read alike.
A different footing
- CUE, a constraint language, in place of markup with imperative extras.
- Modules are values: set any field of one, and the types still check.
- Errors before any provider starts.
Plans and applies
Change one resource. Read one resource.
OpenTofu reads every resource in the state on every plan. pravo starts at your change, follows the recorded dependencies and reads what the change reaches. These are its default settings.
OpenTofu
- resources read from their providers
- 2001
Plan: 1 to add, 0 to change, 1 to destroy.
pravo
- resources read from their providers
- 1
Plan: 1 to add, 0 to change, 1 to destroy.
Graph: planned 1 of 2001, 2000 unchanged and skipped, 1 refreshed, 2000 not read.
The same estate written for each tool: local files in one state, applied, then one value changed. pravo ran with its default settings. Both plans propose the same change. In the Graph line, refreshed means read from the provider and not read means left alone.
Why it matters
Short plans, short applies
Each record names the resources it depends on. A plan starts at your change and follows those dependencies for as long as a value changes. It looks at what the change can touch and leaves the rest alone. You review a plan about your change, and the apply locks and writes only that.
What a plan reaches Refresh ageA staleness budget
Each record remembers when its provider was last asked, and the refresh age says how stale it may be before it is read again. A plan that looks at a record within its age does not ask again, so repeated plans cost fewer calls. A resource can set its own age as a standing promise, and a scheduled pravo drift --due reads the records that are past theirs, oldest first, for a cost you cap. The default age is zero: what a plan looks at is read.
Calls follow your change
Reach decides what a plan looks at. The refresh age decides whether what it looks at is read, and how overdue a record must be before a scheduled sweep reads it. The calls to your providers follow the change and the ages you chose, not the number of resources in the state.
Cost follows the change For a teamOne state for a large estate
Split an estate by ownership, permissions or blast radius, not to make plans fast. People and CI can apply to different parts of one state at the same time, because an apply locks only the resources it writes; an overlapping apply is refused, not queued. Drift is checked on a schedule with pravo drift, not on every plan.
The trade-off A resource that a plan does not reach is not read, so drift in it is found by pravo drift or by a plan that reaches it.
What grows with the estate pravo evaluates the whole specification and opens every record on each run, it reads every data source unless a refresh age is set, and a change that reaches every resource costs what the estate costs.
Take it for a spin
Run it, bring your own stack, or learn CUE first.
Or install from a terminal
All downloads
Run a plan
Install pravo, write one file, plan it, apply it, change it and destroy it. 12 minutes, with no cloud account.
Quick start Bring your ownTry your OpenTofu or Terraform stack
Translate the HCL, convert the state file and plan until the plan is empty. Every step before your first apply leaves your infrastructure and your OpenTofu state as they are.
Migrate Learn firstNew to CUE
CUE from zero in 14 lessons and 136 minutes. Then pravo in 10 lessons and 136 minutes. CUE is a general configuration language, so the first track is useful beyond pravo.
LearnAbout
Why I built this.
I'm Marcin Wyszynski. I co-founded Spacelift and OpenTofu, and I had been exploring CUE for a while. Ten years of running Terraform and OpenTofu left me with notes on what I would do differently, and one day I decided to throw some tokens at the problem and see what gives.
pravo is the result. The notes are mine, and the code was written with Claude from them. It is a study: it drives real OpenTofu providers and its behaviour is documented and tested, but nobody runs it for real yet. It is a non-commercial side project, not a product or a company, released under Apache 2.0 for anyone to use, change and build on. Take it for a spin and tell me what you found.