basquetWi + New ticket
movilba MOVILBA-493

El resolver de fabricantes no trimea el lado catalogo: una fila viva con espacio al final es un destino de resolucion permanentemente muerto, y el CHECK que lo prohibe no lo impidio

Done high dmdb04-mba-l-ca

Medido por db02-mba-l-cc el 2026-09-18 al ejercitar el control contra el libro de la poda de fabricantes (MOVILBA-489, dev 6d80156c). HECHO 1. El resolver compara el lado catalogo SIN trimear. En rutap.proyectar_snapshot: LEFT JOIN public.fabricantes mf ON upper(mf.fabricante) = replace(upper(btrim(s.motor)),'MERCEDEZ','MERCEDES') AND mf.borrado_en IS NULL. El lado libro lleva btrim, el lado catalogo no. Misma forma en el detector de issues del import. HECHO 2. Las 13 filas que la poda de fabricantes borro en PROD terminan TODAS en espacio. Contra esos 13 nombres, el predicado tal cual esta escrito da FALSE en 13 de 13; con btrim del lado catalogo da TRUE en 8. CONSECUENCIA. Una fila con espacio al final es inalcanzable POR SU FORMA, este marcada o no. Para las filas MARCADAS no hace dano (igual se borran). Para una fila VIVA con espacio al final si: es un destino de resolucion permanentemente muerto, el LEFT JOIN da NULL, no hay error, y el sintoma aparece como un dato sin resolver lejos de la causa. HECHO 3, SIN EXPLICAR. public.fabricantes tiene un CHECK fabricantes_nombre_sin_espacios_chk que es exactamente fabricante = btrim(fabricante, espacio/tab/nl/cr/nbsp). Si estuviera validado, esas 13 filas no podrian existir. O la constraint es NOT VALID, o las filas son anteriores a ella, o el caracter final no esta en el juego de btrim. Medir cual de las tres antes de decidir el arreglo. QUE HACE FALTA, en orden: (1) medir cuantas filas VIVAS de fabricantes tienen espacio al principio o al final, en DEV y en PROD - si es cero, esto es prophylactico y baja de prioridad; (2) resolver el hecho 3 con pg_constraint.convalidated antes de tocar nada; (3) recien ahi decidir entre normalizar el dato o poner btrim en el resolver - normalizar el dato es lo que hace que la constraint vuelva a significar algo, poner btrim en el resolver esconde el dato sucio. NO es parte de 489: la poda ya corrio y no perdio nada, las 8 filas nombradas por el libro tenian gemela viva, verificado fila por fila. Se relaciona con MOVILBA-490 (enumerar las resoluciones por valor).

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 4w ago
  • wi-cli-venus note · 4w ago
    Primera medicion (db02, SELECT-only, DEV+PROD, 2026-09-18). LAS TRES HIPOTESIS DEL ITEM SON TODAS FALSAS Y LA EXPLICACION ES UNA CUARTA QUE NO ESTABA EN LA LISTA: en PROD el CHECK NO EXISTE. pg_constraint contype='c' sobre public.fabricantes devuelve CERO filas en PROD. En DEV existe y ademas esta VALIDADO (convalidated='t'): CHECK ((fabricante = btrim(fabricante, chr(32)||chr(9)||chr(10)||chr(13)||chr(160)))) - o sea cubre espacio, tab, LF, CR y NBSP. No es NOT VALID, no es que las filas sean anteriores, no es un caracter fuera del set de btrim: las 13 filas sucias existieron en PROD porque en PROD nada las frenaba. Esto es DRIFT DE ESQUEMA DEV->PROD, misma familia que rutap.v_snapshot_catalog_preflight (MOVILBA-479), y por una vez la direccion de convergencia es obvia: el objeto que falta es el que protege. EL PASO BARATO QUE PEDIA EL ITEM: fabricantes VIVOS con espacio adelante o atras = CERO en las DWS bases. Medido de tres formas para no depender de una: 'fabricante <> btrim(fabricante)' (default), regex '^\s|\s$', y btrim con el set completo incluyendo NBSP - los tres dan 0 sobre 37 vivas en DEV y 37 en PROD. La consulta que lista las filas sucias devuelve 0 filas en las dos bases. O sea el dato ya esta limpio: la poda de 489 se llevo las 13 de PROD y la de DEV nunca las tuvo. CONSECUENCIA: 493 deja de ser un item de DATOS y pasa a ser un item de ESQUEMA. No hay nada que normalizar. Lo que falta es el CHECK en PROD, y este es el momento mas barato posible para ponerlo porque PROD tiene CERO filas vivas que lo violarian: entra como VALIDATED sin trabajo previo. Si se espera, cualquier carga futura vuelve a meter una fila con espacio y el costo sube. NO SE APLICO NADA. Agregar el CHECK a PROD es DDL de PROD y pide GO explicito de Elazar - relevado a el por CLI. Baja de prioridad como item de datos; sigue abierto como item de esquema.
  • wi-cli-venus audit-finding · 4w ago
    CUARTO CANDIDATO, y desplaza a los tres del cuerpo del ticket: la constraint probablemente NO EXISTE EN PROD. Lo levanto audit-mba-l-cc y coder03 lo verifico contra la fuente trackeada (dev 6d80156c), no contra la base. 1. fabricantes_nombre_sin_espacios_chk aparece en UN SOLO archivo de migracion: docs/migrations/2026-09-08-movilba-381-fabricantes-DEV.sql. No hay hermano PROD en docs/migrations/ y 381 no figura en pending-prod-changes.md. 2. Ese archivo es DEV-ONLY POR GUARDA DURA, no por convencion de nombre: aborta si current_database() <> 'movilba_dev' o si el host no es 10.9.10.54. No puede haber corrido en PROD. 3. Su ADD CONSTRAINT no lleva NOT VALID y va precedido de un preflight que RAISE EXCEPTION si alguna fila ya viola. Si hubiera corrido, no podria haber dejado violadores. Eso explica 13 violadores en PROD y 0 en DEV sin necesitar ninguna de las tres hipotesis originales: en DEV la constraint existe y limpio el dato, en PROD nunca se instalo. PRIMER PASO REORDENADO, mas barato que el conteo de whitespace: preguntar pg_constraint por conname='fabricantes_nombre_sin_espacios_chk' en movilba y en movilba_dev, con convalidated. Si falta en PROD, esto deja de ser una hipotesis sobre la constraint y pasa a ser DRIFT DE ESQUEMA (MOVILBA-479) con una limpieza de dato adelante: la constraint no se puede instalar en PROD hasta que los violadores se normalicen, y normalizarlos es un write de datos de PROD con su propio GO. OJO AL PORTAR 381 TAL CUAL: ademas de la CHECK trae un UNIQUE normalizado total y el borrado fisico de 12 ids de fabricantes elegidos a mano contra el estado de DEV del 09-08. Esos 12 ids son de DEV, y PROD ademas ya perdio 13 filas por la poda de 489 hoy. Portar el archivo entero es incorrecto: lo portable es la CHECK y el UNIQUE, re-medidos contra PROD.
  • wi-cli-venus note · 4w ago
    Segunda medicion (db02): NO falta UN objeto en PROD, faltan LOS DOS que crea la migracion 381, y el que falta de verdad no es el CHECK. Comparado pg_indexes+pg_trigger entre las dos bases: PROD no tiene ni 'fabricantes_nombre_sin_espacios_chk' ni el indice 'ux_fabricantes_nombre_normalizado' (UNIQUE sobre upper(btrim(fabricante, espacio/tab/LF/CR/NBSP)), SIN clausula WHERE, o sea sobre todas las filas). Los otros tres objetos (pkey, idx_borrado_en, uq_fabricantes_fabricante_active) y el trigger de soft-delete estan iguales en las dos. ESE INDICE ES LA CAUSA RAIZ, no el CHECK. 'uq_fabricantes_fabricante_active' es UNIQUE sobre el texto CRUDO y solo entre vivas: deja convivir 'SCANIA' y 'SCANIA ' sin protestar porque son cadenas distintas. El indice normalizado es el unico que las colisiona. Por eso PROD junto 13 duplicados por espacio y DEV cero: en DEV los dos objetos existen desde 381 y el CHECK ademas esta VALIDATED. PROVENIENCIA, corroborada por dos instrumentos distintos: audit encontro por el lado del repo que 'docs/migrations/2026-09-08-movilba-381-fabricantes-DEV.sql' no tiene hermano PROD en ninguna parte del arbol trackeado y no figura en pending-prod-changes.md; yo lo confirme por el lado de la base con pg_constraint/pg_indexes. El archivo crea los dos objetos en las lineas 126-127 (ADD CONSTRAINT, sin NOT VALID) y el indice normalizado, y su propio POST exige convalidated. Nunca se porto a PROD. LOS DOS OBJETOS ENTRAN LIMPIOS HOY, medido: 37 filas en PROD, 37 normalizados distintos, cero colisiones bajo la misma expresion del indice (consulta GROUP BY ... HAVING count(*)>1 devuelve 0 filas), y cero filas vivas con espacio. El CHECK entra VALIDATED y el UNIQUE construye sin conflicto. Es la ventana mas barata que va a haber y se abrio recien ahora, cuando 489 se llevo las 13 sucias. NO SE APLICO NADA: son dos DDL de PROD y piden GO explicito de Elazar. Relevado por CLI.
  • wi-cli-venus note · 4w ago
    Ensayo de dcbbdb47 (docs/migrations/2026-09-18-movilba-493-check-fabricantes-sin-espacios-DEV-Y-PROD.sql, md5 d694d57c23847456a6a5ac4643f22551) corrido por db02 en las DOS bases, ROLLBACK, nada aplicado. DEV: la constraint y el indice ya existen, reporta sin_cambio x2 y no toca nada, y los DOS controles negativos disparan igual (el CHECK rechaza el espacio al final, el indice rechaza el duplicado por normalizacion) - o sea la pata 'verifica' tampoco es una linea verde. PROD: instala los dos, POST constraint presente y VALIDADA, indice presente/unico/valido, total 37 -> 37, 0 filas del control sobrevivientes. Violadores 0 y colisiones normalizadas 0 en las dos bases. PRECISION sobre el control negativo del indice, no bloqueante: el handler atrapa unique_violation a secas, sin mirar QUE objeto la levanto. Si otro indice unico reclamara la excepcion primero, el control reportaria exito sin haber ejercitado el indice nuevo. Hoy no puede pasar y se discrimino EMPIRICAMENTE en vez de razonarlo: en PROD, que todavia no tiene el indice, el mismo fixture ('agrale', derivado de 'AGRALE' por min(id)) entro SIN error bajo BEGIN/ROLLBACK. Entonces el rechazo que vio el ensayo vino del indice nuevo y de ningun objeto preexistente. Para hacerlo self-verifying: GET STACKED DIAGNOSTICS CONSTRAINT_NAME y exigir el nombre. Sigue esperando GO de Elazar (dos DDL de PROD).
  • wi-cli-venus note · 4w ago
    Ensayo de 71c86325 (endurecimiento de los dos controles negativos) corrido por db02 en las DOS bases, ROLLBACK, nada aplicado. Identico a dcbbdb47 salvo que ahora cada control exige el NOMBRE del objeto que levanto la excepcion via GET STACKED DIAGNOSTICS CONSTRAINT_NAME y aborta si no coincide. DEV (pata verify, los dos objetos ya presentes): los dos controles disparan y nombran bien su objeto, o sea no hay drift entre el nombre que el archivo asegura y el que la base tiene. PROD: instala los dos, CHECK presente y VALIDADO, indice presente/unico/valido, total 37 -> 37, controles disparados y nombrados. Sigue esperando GO de Elazar.
  • wi-cli-venus note · 4w ago
    APLICADO en las DOS bases (71c86325, apply=true), GO de Elazar en CLI 2026-09-18. PROD: instalados los dos objetos (constraint + indice normalizado). DEV: ya existian, reporta sin_cambio x2 y no los toca. Verificado post-commit por consulta independiente al catalogo en las dos bases: fabricantes_nombre_sin_espacios_chk con convalidated='t', y ux_fabricantes_nombre_normalizado con indisunique='t' e indisvalid='t'. Los dos controles negativos dispararon en las dos bases y cada uno nombro su propio objeto via GET STACKED DIAGNOSTICS CONSTRAINT_NAME, o sea se vio rechazar, no se asumio. Totales sin cambio: DEV 30, PROD 31 (el numero es 30/31 y no 37 porque MOVILBA-496 corrio antes y borro los fabricantes sin uso). Cierra el item por el lado del esquema: PROD ya no puede aceptar un fabricante con espacio ni un duplicado que solo difiera en espacios o mayusculas.
  • wi-cli-venus assigned · 3w ago
    db04-mba-l-ca
  • db04-mba-l-ca completed · 3w ago
    db04-mba-l-ca 2026-09-24 04:00 ART — SELECT-only installed-object verification: DEV movilba_dev and PROD movilba each have public.fabricantes.fabricantes_nombre_sin_espacios_chk with convalidated=true and ux_fabricantes_nombre_normalizado with indisunique=true, indisvalid=true. Live fabricante rows violating the same trim character set: DEV 0/30, PROD 0/31. The PROD schema safeguard requested by this ticket is installed in both named bases; no DB write performed in this sweep.
  • db04-mba-l-ca progress · 3w ago
    db04-mba-l-ca 2026-09-24 04:02 ART — Timestamp correction only: the immediately preceding sweep body on this item carried a future 04:04/04:07 time; the BWI event createdAt is authoritative. No change to its measured findings or status.
  • audit-mba-l-ca audited · 3w ago
    audit pass: Sampled re-close: installed fabricantes safeguards and live-row cleanliness in DEV and PROD; no fresh write/negative-control test
    payload
    {"scope":"Sampled re-close: installed fabricantes safeguards and live-row cleanliness in DEV and PROD; no fresh write/negative-control test","outcome":"pass","evidence":"audit-mba-l-ca | 2026-09-24 04:10 ART. Independently queried movilba_dev and movilba via the configured DB URLs. Both public.fabricantes tables have fabricantes_nombre_sin_espacios_chk convalidated=true with the stated trim character set, plus ux_fabricantes_nombre_normalizado indisunique=true/indisvalid=true. Live trim violators: DEV 0/30, PROD 0/31. This reproduces the completed event. Its future self-timestamp is corrected by db04's later progress event; server createdAt controls chronology. Historical BWI notes document named negative controls; this sweep did not rerun them.","reportRef":null}
bug
4w ago by wi-cli-venus
3w ago
2026-09-24 06:59