feat(migration): refuse a full re-import that would delete native rows
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m51s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m42s

A full run_all.py pass truncates and rebuilds every table it owns from the
Access extract. That was harmless while the platform was a read-only mirror --
every row came from the extract, so wiping and rebuilding lost nothing. It
stopped being harmless once the platform started minting rows Access has never
heard of: allocated portal NUMids, customers created in the staff UI,
OCR-captured policies, app-booked ledger rows, uploaded documents.

REIMPORT is a button in /operaciones, so that was one click away.

native_guard.py counts what only exists here and exits 3; run_all.py runs it
before the first truncate and stops. Detecting an allocated NUMid needs the
staged Parquet -- the customer holds an ordinary-looking (utilities, DATGRAL,
'1172') ref, so "customer has no refs" cannot see it and only comparing against
the extract can. Missing staging is therefore treated as blocking rather than
as "nothing to protect".

The guard does not teach full mode to preserve anything: --sync already upserts
legacy rows against the existing refs and leaves the rest alone, and rebuilding
that inside full mode would re-implement it. --force-full (checkbox in the
REIMPORT confirm, recorded in the audit log) deletes them deliberately.

Verified against dev: clean before, exit 3 listing utilities/1172 with a
synthetic ref present, clean again after removing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 21:01:01 -07:00
co-authored by Claude Opus 5
parent 6a97242fc3
commit 17d83291c3
10 changed files with 319 additions and 20 deletions
+3 -2
View File
@@ -56,8 +56,9 @@ pool AS (
)
SELECT
p.numid, p.cid AS customerUuid, p.name, p.archived, p.hasEmail,
-- EXISTS, not a join: one customer can hold several insurance refs (DATGRAL and
-- COBRO3 both), and joining them fans this result out past one row per NUMid.
-- EXISTS, not a join: 16 customers hold more than one insurance ref (several
-- insurance rows folded into one customer), and joining them fans this result
-- out past one row per NUMid — 1,188 rows for a 1,171-id pool.
EXISTS(SELECT 1 FROM customer_legacy_refs i
WHERE i.customerId=p.cid AND i.sourceSystem='insurance') AS insRef,