One evolving migration.
One safe deployment.
onwardpg plans the schema old and new code share, captures the decisions PostgreSQL cannot infer, and restacks the same migration as your feature and its base evolve.
brew install jokull/tap/onwardpg
Two capabilities. One workflow.
Plan compatibility.
Capture intent as it evolves.
The SQL matters. So does remembering why it is correct when models change, another migration lands, or a branch-worn development database tells a different story.
The core product
One command holds the feature together.
onwardpg plan is not a stateless “here’s the diff, good luck.” The same command grows from a nullable column to an agent-authored type bridge beneath views and indexes, while onwardpg keeps dependency order, intent, and convergence proof intact.
$ onwardpg plan checkout-preferences
DURABLE H → W
accepted base 8 bundles replayed
working schema materialized from Drizzle
plan_id plan_a71…
decision rename carried forward
DEVELOPMENT D → W
preserved table from another branch
local question rename draft-only column?
✓ base moved: same plan restacked
✓ 2 scoped decisions still valid“My models changed again.”
Revise the same PlanID.No stack of speculative fixup migrations.“Another migration landed.”
Restack on replayed history.Carry valid decisions; invalidate changed meaning.“My dev DB is ahead.”
Keep D → W separate.Preserve other-branch objects and local-only history.The migration journey
Your deployment sits in the middle—where it belongs.
Expand
Add the compatible surface while old code is still live.
Deploy
Ship one application version that understands both shapes.
Drain
Wait for old instances, workers, queues, and writes to finish.
Contract
Remove compatibility objects and converge on the desired schema.
$ onwardpg plan add-booking-status
{"status":"needs_action","durable":{"status":"needs_input","generated_plan":{"questions":[{"choices":["assert_only","manual_sql","split_plan"]}]}}}
$ onwardpg plan add-booking-status \
--hint '{"kind":"reconcile","object":"column","name":["app","bookings","status"],"strategy":"manual_sql"}' \
--hint '{"kind":"manual_sql","action":"reconcile_contract_sql","object":"column","name":["app","bookings","status"]}'
{"status":"needs_action","durable":{"status":"needs_sql_edits","edit_files":["phases/contract.sql"]}}
EXPAND
ALTER TABLE "app"."bookings" ADD COLUMN "status" text;
—— deploy application, then drain old traffic ——
CONTRACT
-- PRODUCT-SPECIFIC SQL: Provide reviewed reconcile_contract_sql SQL for app.bookings.status
-- Verify: SELECT NOT EXISTS (SELECT 1 FROM "app"."bookings" WHERE "status" IS NULL);
DO $onwardpg$ BEGIN IF NOT COALESCE((SELECT NOT EXISTS (SELECT 1 FROM "app"."bookings" WHERE "status" IS NULL)), false) THEN RAISE EXCEPTION 'onwardpg contract gate failed: data:1c16b884027de910'; END IF; END $onwardpg$;
ALTER TABLE "app"."bookings" ALTER COLUMN "status" SET NOT NULL;
Trust the evolving artifact
SQL you can inspect.
Intent that survives a rebase.
The bundle captures every consumed hint, edited pocket, hazard, assertion, and PostgreSQL major. Still-valid intent moves forward; stale answers fail.
- ✓ Forward-only expand and contract SQL
- ✓ Fingerprint-bound human decisions
- ✓ Boolean data assertions
- ✓ Empty residual diff required
Why onwardpg
Built for the uncomfortable changes.
Compatibility-aware
Plan the shared schema
Generate expand and contract around the one application version that must understand both shapes.
Development-aware
Keep one plan alive
Capture intent, revise the same PlanID, and restack it as the feature and accepted base evolve.
Catalog-native
Know which diff this is
Keep replayed history, materialized working DDL, local development, production drift, and verification evidence distinct.
Bring your own schema source
Works after your framework, not instead of it.
Export complete PostgreSQL DDL from the tool you already use. onwardpg takes it from there.
Built for coding agents
Let the agent bring context.
Make the planner constrain it.
Agents can supply semantic decisions up front, compose product-aware backfills, and read structured plan output. onwardpg consumes only intent that matches a real decision and rejects stale or unused guesses.
Provide known rename, drop, rollout, or backfill intent on the first plan call.
Use restricted read-only MCP queries for counts and value shapes—not copied rows or write authority.
Turn observed categories into synthetic VALUES assertions without retaining production data.
Ready for the next change?