Skip to content
onwardpg
Esc
navigateopen⌘Jpreview
On this page

Decisions and manual work

How onwardpg handles intent and product semantics that catalogs cannot prove.

Two schema snapshots show facts, not intent. If display_name disappears while full_name appears, that could be a rename, a replacement with different meaning, or two unrelated changes. onwardpg stops and prints a semantic hint.

Confirm a rename

onwardpg plan rename-display-name \
  --hint '{"kind":"rename","object":"column","from":["app","accounts","display_name"],"to":["app","accounts","full_name"]}'

For an eligible same-type rename, the planner can create a temporary second column and deterministic dual-write bridge. Existing rows still need an explicit strategy: reviewed manual SQL, an acknowledged single transaction for a known-small table, or a split plan.

Change text to integer

The catalog proves the types differ. It cannot decide whether an empty string becomes NULL, fails, or maps to a product value.

After the type_change hint selects manual_sql, actual generated output contains two blocking pockets:

-- expand.sql
-- ONWARDPG TODO: replace this comment with reviewed EXPAND SQL for column:app:accounts:age (text -> integer).
-- Establish both old and new interfaces, synchronization/conflict behavior, and any initial backfill while old code is live.
-- Do not use a direct ALTER TYPE here: this plan surrounds one rolling application deployment.

-- contract.sql
-- ONWARDPG TODO: replace this comment with reviewed CONTRACT SQL for column:app:accounts:age (text -> integer).
-- After pre-deployment writers drain, perform final catch-up/assertions, remove compatibility objects, and converge to PostgreSQL type integer.

The developer supplies a real compatibility bridge in those pockets. The conversion rule inside that bridge might be NULLIF(trim(age), '')::integer; a bare in-place ALTER TYPE would not keep both application contracts available during a rolling deployment. Add assertions for every data-dependent assumption:

-- onwardpg:assert account_age_is_convertible
SELECT NOT EXISTS (
  SELECT 1
  FROM app.accounts
  WHERE age IS NOT NULL
    AND trim(age) !~ '^[0-9]+$'
);

A minimal manual_sql hint carries only the decision and lets onwardpg create a marked pocket in the phase file. A coding agent may instead attach complete typed work, including reviewed statements and Boolean verification SQL. In either form, onwardpg accepts the resulting artifact only after clone verification succeeds.

Why decisions expire

Consumed hints are captured automatically in decisions.json; developers and agents do not copy internal fingerprints or resubmit accepted answers. Answers are bound to the exact participating graph scope. Unrelated schema erosion preserves them, while a later edit that changes their meaning makes them stale. This prevents a previous approval for one destructive shape from silently authorizing another.

An agent that already understands the code change may provide several hints on the first plan call. onwardpg consumes only those that match real decisions and rejects an unused guess. See agent-assisted planning.

Was this page helpful?