Same providers, same plan/apply loop. Rebuilt for scale, speed, security and discoverability. Keep scrolling.

12 minutes · no account · no cloud

  • State per resource
  • Sealed and signed
  • Same providers
main.cuevalid
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}
terminal
$pravo plan
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

One definition, two instances and the plan for both. The file and the output were recorded from pravo 0.1.0-dev with cue v0.17.1.

Scroll

See it

Understand your infrastructure at a glance.

pravo serve opens a read-only viewer on your machine. These pages come from a sealed state with three members, a stored plan, two policies and four recorded runs.

localhostread-only
The viewer showing the estate as a graph of groups, resources and the wires between them. 1 2

01 Graph

Everything you manage, in one map. Every resource and what depends on what. Open a group, follow a wire, jump to the code behind a resource.

  1. Open a group
  2. Follow a wire
localhostread-only
A stored plan laid over the graph, with the old and new content of a replaced resource in a side panel. 1 2

02 Plan

What a plan changes. Lay a saved plan over the graph and read each change before anyone applies it.

  1. The resource marked for replacement
  2. The old and the new content, line by line
localhostread-only
The policies page: two rules in force, each with its version, digest, signer and its text in CUE. 1 2

03 Policies

Rules that are written down. Policies are versioned and signed, and each apply names the versions that judged it. The studio tries a rule on a plan first.

  1. Who signed it
  2. The rule itself, in CUE
localhostread-only
The state page: sealed, three members with the roles admin, writer and reader, and an all clear. 1 2

04 State

Who can do what. Members, roles and a check that every record was written by someone allowed to write it.

  1. The check, all clear
  2. Members and their roles
localhostread-only
The timeline of an apply: provider calls, and the network requests captured from the provider, as blue bars.The timeline of an apply: provider calls, and the network requests captured from the provider, as blue bars. 1 2

05 Traces

Where the time went. Every run records its spans, and pravo captures the network requests a provider makes while it works. They appear in the viewer with nothing to set up, or you can send them to your own collector.

  1. Network requests captured from the provider
  2. A provider call

Self-service

Self-service that stays code.

A platform team writes a pattern once, as a definition. Anyone with a checkout fills in its form in the viewer and gets the code of a new instance. One person prepares the plan with a reader key, and someone with a writer key applies it.

service.cuevalid
package estate

import "pravo.dev/pravo"

#Service: pravo.#Template & {
	$template: {
		name:        "service"
		title:       "Web service"
		description: "A service with a generated secret and a rendered config file."
		inputs: {
			name: {}
			owner: {label: "Owning team", description: "The team that gets paged for it. It starts with team-."}
			env: {}
			port: {}
		}
	}
	// Lowercase letters, digits and dashes. It names the config file.
	name: string & =~"^[a-z][a-z0-9-]{2,30}$"
	// The team that answers for the service.
	owner: string & =~"^team-"
	// Where it runs.
	env: *"dev" | "staging" | "prod"
	// The port it listens on.
	port: *8080 | int & >1024 & <65536

	secret: $random.#Resource.random_password & {length: 24}
	config: $local.#Resource.local_file & {
		filename: "out/\(name).env"
		content:  "OWNER=\(owner)\nENV=\(env)\nPORT=\(port)\n"
	}
}
localhostread-only
The form of a new Web service in the viewer: the name checkout, the owning team team-checkout and the env prod. Beside it the code to paste and three checks: the specification loads with it, it adds two resources, and every policy in force holds.
terminal
$pravo plan
pravo will perform the following actions:

  # services.checkout.secret (random_password) will be created
  + services: checkout: secret: {
...
  +     special:     true
...
  # services.checkout.config (local_file) will be created
  + services: checkout: config: {
  +     content:              "OWNER=team-checkout\nENV=prod\nPORT=8080\n"
...
  +     filename:             "out/checkout.env"
...
Plan: 2 to add, 0 to change, 0 to destroy.
Graph: planned 2 · skipped 4 · not read 4
Policies: 1 satisfied.

The template, the form that the viewer builds from it, and the plan of the code it gave, made by a reader. Recorded from pravo 0.1.0-dev with cue v0.17.1.

Take it for a spin

Run it, bring your own stack, or learn CUE first.

pravo is experimental and tested end to end. Try it and decide for yourself.

Download

Download pravo

Or install from a terminal

$curl -fsSL https://pravo.systems/install.sh | sh

Binaries for macOS, Linux and Windows are on the releases page.

Using an agent? Install the pravo skill, so that it writes CUE and not HCL.

About

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 experimental: it drives real OpenTofu providers and its behaviour is documented and tested. It is released under Apache 2.0 for anyone to use, change and build on. Take it for a spin and tell me what you found.