Tool: DataProviderMigrate (CLI)
Version: latest as of 2026-05-20 (installed via dotnet tool)
Provider: postgres (Supabase)
Symptom
A migrate --allow-destructive run fails with:
Error: migration apply failed: 42710: constraint "tenant_credit_event_kind_chk" for relation "tenant_credit_event" already exists
…even when starting from a state where the column and the constraint do not yet exist (verified via information_schema.columns + pg_constraint before the run).
Schema
- name: tenant_credit_event
columns:
- name: kind
type: Text
isNullable: false
checkConstraint: "kind IN ('topup','spend','refund','adjustment')"
Hypothesis
AddColumnOperation with an inline checkConstraint appears to emit two
DDL statements in the same transaction:
ALTER TABLE … ADD COLUMN kind text NOT NULL (which Postgres auto-names the
inline constraint, e.g. tenant_credit_event_kind_check)
ALTER TABLE … ADD CONSTRAINT tenant_credit_event_kind_chk CHECK (kind IN (…))
The second emit collides because the inline form was already applied — OR
DataProvider is emitting the named CONSTRAINT twice in a single batch.
Same shape visible on usage_events where prod has both
usage_events_kind_check AND usage_events_kind_chk (and equivalent
duplicates for charged_micro_usd, vendor_micro_usd, quantity). That's
the smoking gun — a previous successful run left one of each pair behind,
proving DataProvider is creating two constraints per column-level
checkConstraint.
Workaround used (prod hotfix)
Manually applied missing schema deltas via raw SQL (per user authorization
in the credit-billing rollout):
ALTER TABLE … ADD COLUMN kind text NOT NULL (no inline check)
ALTER TABLE … ADD CONSTRAINT tenant_credit_event_kind_chk CHECK … (named, once)
- Renamed
usage_events.ledger_id → credit_event_id
DROP TABLE wallet_ledger, DROP TABLE wallet
- Replaced two combined RLS policies with the four single-op ones declared
in migrations/schema.yaml.
Ask
Either:
- Emit only the named constraint from
checkConstraint: and skip the
inline form, OR
- Detect existing equivalent check constraints (same predicate / same
relation) and treat as a no-op when re-applying.
Cross-refs
- NAP incident: Nimblesite/NimblesiteAgenticPlatform#126
- PR that triggered: Nimblesite/NimblesiteAgenticPlatform#125
Tool: DataProviderMigrate (CLI)
Version: latest as of 2026-05-20 (installed via dotnet tool)
Provider: postgres (Supabase)
Symptom
A
migrate --allow-destructiverun fails with:…even when starting from a state where the column and the constraint do not yet exist (verified via
information_schema.columns+pg_constraintbefore the run).Schema
Hypothesis
AddColumnOperationwith an inlinecheckConstraintappears to emit twoDDL statements in the same transaction:
ALTER TABLE … ADD COLUMN kind text NOT NULL(which Postgres auto-names theinline constraint, e.g.
tenant_credit_event_kind_check)ALTER TABLE … ADD CONSTRAINT tenant_credit_event_kind_chk CHECK (kind IN (…))The second emit collides because the inline form was already applied — OR
DataProvider is emitting the named CONSTRAINT twice in a single batch.
Same shape visible on
usage_eventswhere prod has bothusage_events_kind_checkANDusage_events_kind_chk(and equivalentduplicates for
charged_micro_usd,vendor_micro_usd,quantity). That'sthe smoking gun — a previous successful run left one of each pair behind,
proving DataProvider is creating two constraints per column-level
checkConstraint.Workaround used (prod hotfix)
Manually applied missing schema deltas via raw SQL (per user authorization
in the credit-billing rollout):
ALTER TABLE … ADD COLUMN kind text NOT NULL(no inline check)ALTER TABLE … ADD CONSTRAINT tenant_credit_event_kind_chk CHECK …(named, once)usage_events.ledger_id → credit_event_idDROP TABLE wallet_ledger,DROP TABLE walletin
migrations/schema.yaml.Ask
Either:
checkConstraint:and skip theinline form, OR
relation) and treat as a no-op when re-applying.
Cross-refs