Home / Docs / pg_tenantundo CLI

pg_tenantundo CLI reference

A single Go binary for local, standalone tenant recovery: classify the tenant graph, run the safety preflight, extract a bundle, diff against live data, and execute a hash-approved restore or erase.

Connection and the DSN split

Every command takes a Postgres DSN via -dsn or the TENANTUNDO_DSN environment variable. All read paths (classify, preflight, report, extract, diff, plan) require a read-only role. Only -execute and -erase-execute use a write-capable role — everything else refuses one.

FlagArgumentPurpose
-dsnDSNPostgres DSN; read-only role required. Falls back to TENANTUNDO_DSN.
-roottableTenant root table. Default: tenants.
-overridesfile.yamlYAML mapping of table → class to resolve ambiguous tables.
-jsonEmit the resolution as JSON instead of text.
-preflightRun the safety preflight and print every finding (blocks + warnings).
-reportfileWrite the HTML readiness report to this path.
-extract-tenantidExtract this tenant's closure into the -out bundle directory.
-outpathOutput: a directory for -extract-tenant; a file for -plan / -erase-plan.
-tenantidTenant id (bigint or UUID) for -diff, -plan, -erase-plan.
-bundledirBundle directory for -diff, -plan, -execute.
-diffDiff live state against -bundle for -tenant (missing / changed / extra rows).
-planCompute a merge plan (restore -tenant to bundle state), write it to -out, print its hash.
-executeplan fileExecute a merge plan. Requires -approve and -bundle. Write role.
-approvehashThe approved plan hash for -execute / -erase-execute.
-erase-planPlan erasure of -tenant, write it to -out, print its hash.
-erase-executeplan fileExecute an erase plan. Requires -approve. Write role.

Typical flow

With no mode flag, the tool prints the tenant graph — each table classified as root, owned, shared, ambiguous, or excluded.

# 0. Classify the tenant graph (read-only)
pg_tenantundo -dsn "$RO_DSN"                 # or add -json

# 1. Safety preflight — list every hazard that would block a live restore/erase
pg_tenantundo -dsn "$RO_DSN" -preflight

# 2. Readiness report (HTML)
pg_tenantundo -dsn "$RO_DSN" -report readiness.html

# 3. Extract one tenant's closure to a bundle directory
pg_tenantundo -dsn "$RO_DSN" -extract-tenant 42 -out ./acme-bundle

# 4. Diff live data against the bundle
pg_tenantundo -dsn "$RO_DSN" -tenant 42 -bundle ./acme-bundle -diff

# 5. Write a merge plan and print its approve hash
pg_tenantundo -dsn "$RO_DSN" -tenant 42 -bundle ./acme-bundle -plan -out ./acme.plan

# 6. Execute the approved plan (WRITE role) after a second person approves the hash
pg_tenantundo -dsn "$RW_DSN" -execute ./acme.plan -approve <hash> -bundle ./acme-bundle

Provable erase

# Plan the deletion — prints row/object counts and the approve hash
pg_tenantundo -dsn "$RO_DSN" -tenant 42 -erase-plan -out ./acme-erase.plan

# Execute after approval (WRITE role); emits signed proof, verifies others unchanged
pg_tenantundo -dsn "$RW_DSN" -erase-execute ./acme-erase.plan -approve <hash>

The safety invariant: -execute and -erase-execute refuse to run unless the plan's own hash matches -approve, and both verify that everything outside the target tenant is unchanged before committing. Ambiguous relationships must be resolved via -overrides first.

Run the CLI against a real schema

A Readiness Drill runs this exact flow, read-only, and hands back the graph, the blockers, and a signed report.

Book a recovery drill