basquetWi + New ticket
movilba MOVILBA-380

APLICADO: CHECK de bordes + UNIQUE TOTAL sobre (fabricante_id, upper(btrim(...))) con conjunto de trim explicito - cinco controles y contraprueba de que el btrim viejo dejaba pasar el NBSP

Done normal dmdb02-mba-l

MEDIDO POR db02-mba-l-cc el 2026-09-08 escribiendo MOVILBA-377b, DEV. Corrige una premisa que yo afirme y que era falsa, y al corregirla aparece la causa raiz de todo el item. YO AFIRME: que public.carrocerias esta keyeada por (fabricante_id, carroceria_modelo) y que por eso habia que borrar la id 70 antes de corregir la 71, o se violaba el unico. ES FALSO. public.carrocerias tiene exactamente DOS constraints: PRIMARY KEY (id) y la FK a fabricantes. Sus otros cuatro indices son NO UNICOS. No existe ningun unico sobre (fabricante_id, carroceria_modelo) ni sobre ninguna otra combinacion. Y ESA AUSENCIA ES LA CAUSA RAIZ, no un detalle: es exactamente por eso que el catalogo llego a 74 filas con NUEVE entradas AUTOBUS identicas, diez TODO BUS, siete HEPRICAR, y con 'YUTONG' y 'YUTONG ' conviviendo. La base nunca tuvo como impedirlo. Cada limpieza previa borro filas; ninguna impidio que volvieran a nacer. POR QUE ES EL ITEM QUE HACE PERMANENTE A 377, y por que lo abro: el motivo que dio Elazar para el borrado fisico fue textual "i dont want to have another agent have to do all this aagin". Borrar 56 filas sin agregar la restriccion deja el catalogo limpio y con la misma puerta abierta con la que se ensucio. En seis meses hay otro agente midiendo otra vez cuantas AUTOBUS hay. La restriccion es lo que convierte la limpieza en un estado estable. QUE SE PROPONE, DESPUES de que 377 y 377b esten aplicados y NO antes - la restriccion no puede crearse mientras existan los duplicados que va a rechazar: 1. UNIQUE sobre (fabricante_id, carroceria_modelo) en public.carrocerias. 2. Antes, decidir que hace con el espacio: un unico sobre el texto crudo permite 'YUTONG' y 'YUTONG ' como dos filas distintas otra vez. Evaluar un indice unico sobre (fabricante_id, btrim(carroceria_modelo)) o normalizar en el escritor. Hay que ELEGIRLO, no que salga por descarte. 3. Medir el mismo hueco en las otras tablas del censo de MOVILBA-378 - chasis 132 filas, fabricantes 50, subtipos, tipos - porque si a carrocerias le falta, no hay motivo para suponer que las demas la tienen. Preguntar a pg_constraint e pg_indexes, jamas a la memoria de nadie. CONTROL QUE TIENE QUE ESTAR: crear la restriccion e intentar insertar un duplicado, y que la insercion FALLE, con rollback. Una restriccion que se crea sin que nadie pruebe que rechaza algo no esta probada. DEV. PROD aparte y despues. NO EJERCITADO: el estado de constraints de las otras cuatro tablas, y si el escritor de la app o el import normalizan el texto antes de insertar.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 5w ago
  • wi-cli-venus spec · 5w ago
    ARTEFACTO ESCRITO Y ENSAYADO, NO APLICADO. 2026-09-08 db02-mba-l-cc. GO de Elazar 'go 380' relevado por coder02-mba-l-cc. Archivo: docs/migrations/2026-09-08-movilba-380-unique-y-check-en-carrocerias-DEV.sql md5 81d0b9e0c5420d01936ecca9f3e7f126, 205 lineas. DEV only. DOS OBJETOS: CHECK carrocerias_modelo_sin_espacios_chk: carroceria_modelo = btrim(carroceria_modelo). Gobierna que puede contener UNA fila. UNIQUE INDEX ux_carrocerias_fabricante_modelo sobre (fabricante_id, upper(btrim(carroceria_modelo))). Gobierna que pueden compartir DOS filas. ENSAYO CON ROLLBACK, PASS. El control positivo y sus tres rechazos, cada uno en su subtransaccion y capturando su propio SQLSTATE: (a) duplicado que solo cambia el case -> FALLO en el UNIQUE (23505), que era lo esperado. (b) duplicado con espacio al final -> FALLO en el CHECK (23514), que era lo esperado; tambien violaba el UNIQUE. (c) nombre nuevo con espacio al final -> FALLO en el CHECK (23514), que era lo esperado. El archivo aborta si alguno de los tres INSERT tiene exito O si el rechazo viene del objeto equivocado. POST: 17 filas intactas, los dos objetos instalados, indexdef re-leido de pg_class. DECLARADO EN LA CABECERA EN VEZ DE DISFRAZARLO DE CONTROL: con el CHECK puesto ninguna fila puede llevar espacios, asi que el btrim DENTRO de la clave unica nunca puede ser lo que rechace algo -el CHECK dispara primero-. Queda como defensa para el dia en que alguien saque el CHECK, y el mensaje del control (b) lo dice en vez de atribuirle al unique un rechazo que hizo el CHECK. DECISION DE DISENO QUE HAY QUE MIRAR: el indice es TOTAL, no parcial sobre borrado_en IS NULL. Sigue la politica de 378 (una fila de catalogo esta VIVA o NO ESTA), pero trg_soft_delete_carrocerias sigue armado, asi que un DELETE se vuelve soft-delete y esa fila seguiria ocupando la clave, impidiendo re-crear el mismo nombre. El parcial evitaria ese roce pero dejaria volver la clase de duplicados justo por soft-delete. Se eligio el total. MEDICION SELECT-ONLY DE LAS OTRAS TABLAS DE SCOPE B, cero escrituras, sin proponer nada: UNIQUES EXISTENTES SOBRE LA CLAVE NATURAL: chasis: NINGUNO. Solo chasis_pkey sobre id. El mismo hueco que tenia carrocerias. fabricantes: uq_fabricantes_fabricante_active (fabricante) WHERE borrado_en IS NULL. vehiculos_subtipos_transporte: uq_vehiculos_subtipos_tipo_codigo_active (vehiculo_tipo_id, codigo) WHERE borrado_en IS NULL. vehiculos_tipos_transporte: uq_vehiculos_tipos_codigo_active (codigo) WHERE borrado_en IS NULL. TRES DE LAS CUATRO YA USAN PARCIAL-SOBRE-VIVAS, y ninguna normaliza case ni espacios dentro de la clave. El indice total de carrocerias es inconsistente con ese patron de la casa: se registra como hecho medido, no se reabre la decision. CUANTAS FILAS VIOLARIAN el equivalente de los dos objetos (filas / con espacios / claves duplicadas / filas sobrantes): chasis 132 / 0 / 23 / 32. CERO de las 23 claves duplicadas son entre filas VIVAS: todas involucran las 112 soft-deleteadas de MOVILBA-200. fabricantes 50 / 10 / 9 / 11. vehiculos_subtipos_transporte 14 / 0 / 0 / 0. vehiculos_tipos_transporte 6 / 0 / 0 / 0. Los duplicados de fabricantes son exactamente la clase que este item viene a frenar, y su unique parcial NO los agarra porque clavea sobre el string crudo: CUMMINS 3 filas ([CUMMINS], [CUMMINS ], [CUMMINS ]), SCANIA 3 ([SCANIA], [SCANIA ], [SCANIA ]), y con 2 cada uno AGRALE, EQUIPMAKE, MERCEDES BENZ, WEICHAI, YUCHAI, YUTONG. PATITO ([Patito], [patito]) es el unico par que difiere solo por case y las dos filas estan soft-deleteadas. NO SE PROPONEN CONSTRAINTS EN NINGUNA DE ELLAS: chasis esta bloqueada por MOVILBA-200 y fabricantes por MOVILBA-381. Subtipos y tipos estan limpias y aceptarian los dos objetos hoy, pero eso es el paso 1 de MOVILBA-378 y no tiene GO. SIGUIENTE: audit-mba-l-cc revisa ANTES de aplicar.
  • wi-cli-venus verified · 5w ago
    APPLIED a DEV 2026-09-08 por db02-mba-l-cc con $DEV_DATABASE_URL. Archivo docs/migrations/2026-09-08-movilba-380-unique-y-check-en-carrocerias-DEV.sql, md5 d4cd6ac77650f1f69c85a7ca66178361, 264 lineas, re-verificado en disco antes del apply. Cabecera server_version=15.17 server_addr=10.9.10.54 db=movilba_dev. Controles: (a) duplicado solo-case FALLO en el UNIQUE 23505; (b) duplicado con espacio final FALLO en el CHECK 23514; (c) nombre nuevo con espacio final FALLO en el CHECK 23514; (d) nombre nuevo con NBSP final FALLO en el CHECK 23514; (e) NBSP INTERIOR ENTRO como se esperaba y se revirtio por Z0380. Contraprueba aparte y revertida: con CHECK+UNIQUE reconstruidos sobre btrim de UN argumento, el INSERT de (d) ENTRO. El agujero del default esta medido, no argumentado. Post-apply leido de la base en sesion aparte: carrocerias_modelo_sin_espacios_chk convalidated=t; ux_carrocerias_fabricante_modelo indisunique=t indisvalid=t; 17 filas, 17 vivas, 0 con espacio sobrante, 17 claves distintas; trg_soft_delete_carrocerias en O. md5+length de los 17 modelos capturados antes y re-leidos despues: diff vacio, ninguna fila alterada. NO EJERCITADO: PROD (PG 17.6) intacto; U+2000-200A, U+3000 y U+FEFF no estan en el conjunto nombrado y no fueron probados. Escritor (medicion de lectura, sin cambios): existe uno solo, src/repositories/carrocerias.ts create():176 y update():255 detras de /api/admin/carrocerias; ningun tool de import escribe la tabla. Ambos hacen .trim() de JS, que saca NBSP y mas, o sea mas ancho que el conjunto de la base: el CHECK no puede romperle un alta. El UNIQUE si queda sin manejar - no hay chequeo previo de duplicados ni mapeo de 23505 en src/services/carrocerias.ts, asi que un alta que difiera solo en case tira error de base crudo. Superficie de app, item aparte a criterio de coder02.
  • wi-cli-venus note · 5w ago
    APLICADO EN DEV. db02-mba-l-cc, 2026-09-08 09:46 ART, md5 d4cd6ac77650f1f69c85a7ca66178361, 264 lineas, re-verificado en disco inmediatamente antes de correr: los mismos bytes que leyo audit. server_version 15.17, 10.9.10.54, movilba_dev, movilba_user. PROD sin tocar. LOS CINCO CONTROLES, cada uno atribuido a su objeto: (a) duplicado solo por mayusculas -> FALLO en el UNIQUE (23505). (b) duplicado con espacio final -> FALLO en el CHECK (23514). (c) nombre nuevo con espacio final -> FALLO en el CHECK (23514). (d) nombre nuevo con NBSP final -> FALLO en el CHECK (23514). (e) NBSP INTERIOR -> ENTRO, revertido por subtransaccion. Prueba que la regla es de BORDES y no rechaza nombres legitimos como MERCEDES BENZ o TODO BUS. LA CONTRAPRUEBA DE (d), y es lo que hace que el endurecimiento este PROBADO y no argumentado: db02 reconstruyo CHECK y UNIQUE con `btrim` de UN argumento y corrio el mismo INSERT. ENTRO. Con el btrim viejo el NBSP atraviesa los dos objetos. La diferencia entre las dos versiones quedo MEDIDA. Revertido antes del apply. POST-APPLY LEIDO DE LA BASE en una sesion psql aparte, no de la salida del script: `carrocerias_modelo_sin_espacios_chk` (convalidated=t) y `ux_carrocerias_fabricante_modelo` (indisunique=t, indisvalid=t) instalados, con sus definiciones leidas de pg_constraint y pg_index. 17 filas, 17 vivas, 0 con espacio sobrante, 17 claves distintas. Trigger en 'O'. **Ningun modelo alterado**: md5 y length de las 17 filas capturados ANTES y re-leidos DESPUES, diff vacio. COBERTURA, dicha como corresponde: el conjunto nombrado cubre 0x20, tab, LF, CR y NBSP. **NO cubre U+2000-200A, U+3000 ni U+FEFF**, y no se probaron. Una regla con conjunto nombrado cubre lo que enumera y nada mas. PROD (PG 17.6) no fue tocado y este ensayo no dice nada sobre el. EL ESCRITOR, medido despues del apply (lectura de codigo, sin escrituras): SI existe. src/repositories/carrocerias.ts:176 create() y :255 update(), detras de /api/admin/carrocerias y el admin UI. Ningun tool de import escribe la tabla. Los dos normalizan con `.trim()` de JS (lineas 178 y 243), que saca NBSP y U+2000-200A/U+FEFF - o sea MAS ancho que el conjunto de la base. **El CHECK no le puede abrir un 500 a nadie.** Artefacto commiteado en dev. Se cierra.
  • wi-cli-venus titleChanged · 5w ago
    APLICADO: CHECK de bordes + UNIQUE TOTAL sobre (fabricante_id, upper(btrim(...))) con conjunto de trim explicito - cinco controles y contraprueba de que el btrim viejo dejaba pasar el NBSP
  • wi-cli-venus completed · 5w ago
    completed
task
5w ago by wi-cli-venus
5w ago
2026-09-08 12:48