V176: validate existing NOT VALID foreign keys. Never deletes a row.
Where this comes from
Andi's A002 checked its own premise before writing code: the cascade
UUIDFKColumns.@fk_constraints declares for
phoenix_kit_users_tokens.user_uuid and
phoenix_kit_user_role_assignments.user_uuid already exists and already
works — deleting a user with raw SQL, bypassing every application code
path, cascades correctly on any schema at V164+ (verified in a rolled-back
transaction on a live install). The orphans a prior cleanup removed were
rows created BEFORE the constraint existed (17-24 July; the FK repair
landed 10 August) — exactly what NOT VALID records: pre-existing rows
were never checked, new violations have been impossible since. What never
happened is validating it, because nothing in core ever does.
convalidated appears exactly once anywhere in this codebase before this
file — inside V164, about constraints IT just created, and V164's own
moduledoc says plainly: "re-running it does not retry them … VALIDATE each
by hand." Nobody's hand ever did, and mix phoenix_kit.doctor's Orphaned
FK check does not report this state at all — it looks for orphan ROWS,
which a NOT VALID constraint with zero current violations has none of.
Filed as V175 originally; renumbered to V176 when upstream took V175 for
an unrelated Buckets change (phoenix_kit_buckets.integration_uuid) while
this branch was still in review. No content changed in the move beyond
the version number itself and the COMMENT ON TABLE stamps.
What this does
For every {table, uuid_fk, ref_table, ref_col, _on_delete} in
UUIDFKColumns.fk_constraints/0 (the exact list, not a copy — same reason
V164 shares it rather than re-declaring): if a foreign key of that exact
shape exists — matched by shape (referencing column -> referenced column),
not by name, so a constraint V164 adopted under a different name is still
found — and is NOT VALID:
- zero orphaned rows right now →
VALIDATE CONSTRAINT. The count is checked FIRST specifically so this never attempts aVALIDATEalready known to fail — that is a full table scan underSHARE UPDATE EXCLUSIVEfor nothing. - one or more orphaned rows → left exactly as it is. Nothing here
ever deletes a row or nulls out a reference to force a constraint
through — the same policy V164 states for itself, for the same
reason: this runs against other people's already-deployed data, not
data this call created moments earlier. A warning names the
table/column and the real orphan count
(
PhoenixKit.Migrations.Repair.Probe.orphan_count/6, reused, never a guess), then a one-line end-of-run summary — the per-constraint warnings are easy to lose in a deploy log (same reasoning as V164'sreport_not_valid_fks/1).
Already-valid constraints, and declarations whose table/column (or the referenced table/column) is absent — an uninstalled feature module, same case V164 skips — are silently left alone: nothing to report, this version has no business with either.
Idempotent: a constraint this pass validates reads convalidated = true
on every later run and is skipped instantly (one shape probe, no table
scan). A constraint left NOT VALID because of orphans is re-checked
fresh on every run — the count is a live read, not cached — so once an
operator resolves the rows a re-run validates it with no further action.
down/1
A repair, not a feature — same precedent as V164/V174: rolling back does not un-validate a constraint that is now genuinely clean. Comment restamp only, back to V175 (the Buckets version immediately below this one in the renumbered chain).