basquetWi + New ticket
terra TERRA-200

anestesiaType and regionAnatomicaValue now have ENABLE ALWAYS vocabulary guards

Done normal dtdb02-terra-ca

Completed 2026-09-10 under Elazar's existing GO after audit PASS audit-terra-ca-mtvmpsc22kki. Migration registration 20260910144013 / terra_57_anestesia_region_vocab_guards added two terra-owned BEFORE INSERT OR UPDATE guards: terraPracticas.anestesiaType resolves through resolveScopedVocabulary('terra','terraAnestesia'); terraPracticasRegionesAnatomicas.regionAnatomicaValue resolves through resolveScopedVocabulary('terra','regionesAnatomicas'). Both triggers are ENABLE ALWAYS; functions pin search_path and restrict EXECUTE to postgres/service_role.\n\nPreflight found 0 invalid values (6 practicas, 3 region rows). Rollback-only controls accepted valid scoped values and rejected both invalid values with P0001. Final catalog and residue census are clean. This proves DB paths, not deployed UI behavior.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 6w ago
  • wi-cli-venus note · 6w ago
    RESPUESTA A LA PREGUNTA DE FORMA, medida por db-terra-ca leyendo los cuerpos con pg_get_functiondef y no los nombres, 2026-09-01. LA CONCLUSION ES: HACE FALTA UNA FUNCION NUEVA. Ninguno de los tres guards existentes se puede reusar tal cual sobre anestesiaType ni sobre regionAnatomicaValue, porque ninguno acepta ni lee una categoria parametrizable. DONDE ESTA CLAVADA LA CATEGORIA EN CADA UNO, que es el detalle que decide la respuesta. fn_terraPracticasCamposValueGuard exige campoId y saca la categoria de terraModuloCampos.lookupCategory, o sea que depende de un esquema de campos que estas dos columnas no tienen. fn_terraComplicacionesVocabGuard resuelve la categoria con un CASE hardcodeado sobre NEW.fase. fn_terraParticipantesVocabGuard lleva el literal 'rolEnPractica' adentro. Los tres terminan llamando a resolveScopedVocabulary('terra', <categoria>), pero cada uno calcula esa categoria de una manera que no se puede reapuntar sin reescribir el cuerpo. LO QUE ABARATA EL ARREGLO Y CONVIENE DEJAR ESCRITO: el ayudante compartido y parametrizable YA EXISTE - resolveScopedVocabulary('terra', <categoria>) es exactamente lo que los tres invocan. O sea que la funcion nueva no inventa mecanica: es un cuerpo corto que pasa su categoria a ese ayudante. El costo esta en el trigger y en la migracion, no en la logica. NO SE PROPONE GENERALIZAR LOS TRES EXISTENTES. Lo pedi asi de forma explicita y db-terra-ca lo respeto. Motivo: tocar tres guards vivos que hoy protegen tres columnas para acomodar una cuarta es cambiar codigo que funciona por una razon que no es la suya, y ademas cada uno saca la categoria de una fuente distinta, asi que la "generalizacion" seria un CASE sobre nombres de tabla - que es precisamente la forma de censo por lista de nombres que ya fallo dos veces hoy en este proyecto. SOBRE EL SQLSTATE, que no cambia la respuesta pero conviene registrarlo antes de escribir la migracion: los tres RAISE con P0001, que es el generico compartido. Una funcion nueva que reuse P0001 no va a ser distinguible de las otras tres desde el lado de la app. SIGUE SIN MIGRACION Y ES DELIBERADO: terra_55 sigue sin GO en la eq 78, y las invariantes de esta clase esperan ese GO en vez de competir entre si.
  • wi-cli-venus assigned · 6w ago
  • wi-cli-venus assigned · 6w ago
    MEDICION PREVIA ASIGNADA A db02-terra-cc, 2026-09-01 12:36 UTC. NO le asigne el diseño de la migracion: eso sigue siendo de db-terra-ca y el orden de aplicacion lo decide Elazar, con cuatro invariantes medidas y ninguna aplicada a proposito. Lo que reparti es la lectura que tiene que existir ANTES del diseño. LA PREGUNTA QUE MANDE: leer los TRES triggers que ya existen - sobre terraPracticasCampos.value, terraPracticasComplicaciones.value y terraPracticasParticipantes.rolEnPractica - para que el guard de las dos columnas desnudas COPIE una forma viva en vez de inventar una. Seis puntos: definicion entera con pg_get_triggerdef incluido TG_ARGV, cuerpo entero con pg_get_functiondef, contra que tabla y columnas resuelve y si contempla ownerAppKey null, si los tres invocan una funcion parametrizada compartida o son tres funciones distintas, que hace cuando no resuelve (RAISE con que mensaje y que SQLSTATE, o silencio), y si respetan el mode del scope o solo miran existencia del value. POR QUE TG_ARGV ESTA NOMBRADO EXPLICITAMENTE: dos triggers que llaman a la misma funcion con distinto TG_ARGV no son el mismo trigger, y la diferencia no se ve en el nombre. Ya nos costo hoy en la atribucion de escritores. Y POR QUE LE PROHIBI LA RESPUESTA COMODA: le dije que no me diga "los tres son iguales" sin pegar los tres cuerpos. Una conclusion de equivalencia sin los tres textos no se distingue de no haber mirado dos de ellos. TAMBIEN LE MARQUE LA TRAMPA DE MEDICION QUE YA PISAMOS HOY: contar filas de pg_trigger sin filtrar tgisinternal devuelve la implementacion interna de las FK y se lee como que la tabla tiene triggers propios. Si alguno de los tres resulta interno o no existe, eso es el resultado, no un hueco para completar. EL REPARTO ES DELIBERADO: db02 mide, db-terra-ca diseña. Dos carriles sobre la misma fila sin que uno lea la conclusion del otro primero.
  • wi-cli-venus note · 6w ago
    CORRECCION DE DOS DEFECTOS MIOS EN EL EVENTO ANTERIOR, y van juntos porque los dos son del mismo turno. Hora real leida del reloj: 2026-09-01 12:11 UTC. PRIMERO, EL EVENTO VACIO QUE ESTA JUSTO ARRIBA. Ese evento de cuerpo cero no es una asignacion sin razon escrita: es un artefacto de shell. Escribi el cuerpo en un archivo y lo pase con cat adentro del argumento, que es el procedimiento correcto, pero le di la RUTA EQUIVOCADA - apunte al directorio del proyecto en vez del scratchpad real. cat fallo, el argumento quedo como cadena vacia, y wi acepto el evento igual. Ocho segundos despues corri lo mismo con la ruta buena y ese es el evento con contenido. bwi es append-only, asi que el vacio no se puede borrar y queda; esta linea existe para que nadie lo lea como una decision cuyo fundamento se perdio. LO QUE FALLO NO FUE EL PROCEDIMIENTO SINO LA VERIFICACION: la regla que sigo dice pasar el cuerpo por archivo Y DESPUES verificar grepeando una frase del medio. Hice las dos cosas, pero el grep dio cero y lo lei como "todavia no aparece" en vez de como "el cuerpo esta vacio". El chequeo funciono y yo desestime su resultado. Un verificador que uno ignora cuando da rojo no es un verificador. SEGUNDO, Y ES PEOR PORQUE ES SILENCIOSO: las horas que escribi en ese evento - 12:35, 12:36, 12:37 - LAS INVENTE. No lei el reloj; estime cuanto habia pasado desde la ultima hora que tenia en contexto y escribi el resultado como si fuera una medicion. La hora real era 12:11, o sea que me equivoque por veinticuatro minutos EN EL FUTURO, y una hora futura en un ledger inmutable ordena mal los hechos contra cualquier otra fuente fechada. ES EXACTAMENTE LA CLASE QUE VENGO MIDIENDO TODO EL DIA EN CODIGO AJENO - una afirmacion en presente, plausible, sin fuente, que nadie vuelve a medir - cometida por mi en el mismo turno en que la documento. Y la hora es el peor caso de esa clase porque no tiene ningun sintoma: un numero equivocado en una consulta se ve raro, una hora equivocada se ve exactamente igual que una correcta. REGLA QUE QUEDA: la hora se lee del reloj con date -u en la misma corrida que escribe el evento, nunca se deriva del contexto. Y un grep de verificacion que da cero es rojo hasta que se demuestre lo contrario, no ruido. EL CONTENIDO TECNICO DEL EVENTO ANTERIOR NO CAMBIA: las asignaciones, las mediciones de contexto de los carriles y las condiciones del trabajo repartido son correctas. Lo unico falso son las tres horas.
  • wi-cli-venus verified · 6w ago
    LOS TRES GUARDAS EXISTENTES ESTAN LEIDOS Y LA FORMA A COPIAR ESTA IDENTIFICADA. Medido por db02-terra-cc, RE-CORRIDO POR MI contra pg_trigger y pg_get_functiondef antes de escribir esto - no tome el reporte por bueno en ninguna de las dos partes verificables. LOS TRES SON REALES Y NO INTERNOS, confirmado por mi con tgisinternal = false: trg_terraPracticasCampos_valueGuard sobre BEFORE INSERT OR UPDATE OF value y campoId; trg_terraPracticasComplicaciones_vocabGuard sobre value y fase; trg_terraPracticasParticipantes_vocabGuard sobre rolEnPractica. NINGUNO USA TG_ARGV - las tres definiciones son EXECUTE FUNCTION sin argumentos. Eso importa porque era justamente la pregunta que hice para no repetir el error de hoy, y la respuesta es que aca no aplica. TRES FUNCIONES DISTINTAS, NO UNA PARAMETRIZADA, y esto cambia el costo del arreglo: cada una deriva su categoria de forma propia. Complicaciones la saca de NEW.fase con un CASE entre intra y post; Participantes usa la categoria fija rolEnPractica; Campos la resuelve via terraModuloCampos.lookupCategory con JOIN POR campoId, no por nombre - que es la forma correcta y la misma regla de agrupar por identificador que ya nos costo una correccion hoy. PERO EL NUCLEO SI ES COMPARTIDO, y esa es la forma que las dos columnas desnudas tienen que copiar: las tres llaman a resolveScopedVocabulary('terra', categoria). LA LEI ENTERA CONTRA LA BASE y el cuerpo que reporto db02 es textual. Dos compuertas en orden: primero PROPIEDAD - ownerAppKey null o igual al appKey, con el comentario propio de la funcion diciendo que los valores privados de otra app no son visibles y que ningun scope puede exponerlos; despues VISIBILIDAD - mode null significa sin scope y todos los activos, include exige membresia, exclude exige ausencia. O sea que el respeto del mode VIVE ADENTRO DE LA FUNCION COMPARTIDA y no en cada trigger, que es exactamente lo que hace barato el guard nuevo: no hay que reimplementar la logica de scope, hay que llamar a esta. CAMPOS HACE MAS QUE LAS OTRAS DOS y conviene no copiarlo entero por inercia: valida ademas que el campoId exista, que el campo este isActive, y que pertenezca al scope de la cirugia via terraCirugiaCampos, todo ANTES de tocar vocabulario. Eso es especifico de esa tabla. FALLAN FUERTE, NUNCA EN SILENCIO: las tres hacen RAISE EXCEPTION USING ERRCODE = 'P0001', con prefijo terra: y el valor y la categoria interpolados en el mensaje. Campos suma tres RAISE propios mas, tambien P0001. QUE QUEDA PARA db-terra-ca, Y SIGO SIN ASIGNARLO: el diseño de los dos guardas faltantes - anestesiaType y regionAnatomicaValue - ahora tiene una forma viva que copiar en vez de una que inventar. El orden de aplicacion lo decide Elazar: hay cuatro invariantes medidas y ninguna aplicada, a proposito, para que se secuencien en vez de competir. NADA DE ESTO PROPONE UNA MIGRACION Y NADA SE APLICO.
  • wi-cli-venus assigned · 5w ago
    db-terra-cc
  • wi-cli-venus assigned · 5w ago
    db02-terra-ca
  • wi-cli-venus assigned · 5w ago
    db-terra-cc
  • wi-cli-venus assigned · 5w ago
    db02-terra-ca / db
  • wi-cli-venus note · 5w ago
    HANDOFF db-terra-cc -> db02-terra-ca, orden directa de Elazar en CLI 2026-09-10. Marker HOFF-200-mtvfq. Sin trabajo en vuelo de mi parte: nada escrito, nada aplicado, ningun draft. Contexto durable en memoria terra-unguarded-vocab-columns-anestesia-region.md; ver tambien terra198-vocab-scope-guard-blocks-new-unscoped-seeds.md antes de proponer un seed nuevo.
  • wi-cli-venus titleChanged · 5w ago
    anestesiaType and regionAnatomicaValue now have ENABLE ALWAYS vocabulary guards
  • wi-cli-venus descriptionChanged · 5w ago
    was: MEDIDO CONTRA LA BASE VIVA 2026-09-01 10:37 UTC. Sale de TERRA-198 y se abre aparte porque NO ES LA MISMA EXPOSICION: alla el unico escritor es una migracion corriendo como postgres, aca el escritor es la app por RPC. EL HECHO. terra tiene cinco columnas de texto que referencian vocabulario SIN FK. Tres estan protegidas por trigger de validacion en cada INSERT/UPDATE: terraPracticasCampos.value con fn_terraPracticasCamposValueGuard, terraPracticasComplicaciones.value con fn_terraComplicacionesVocabGuard, y terraPracticasParticipantes.rolEnPractica con fn_terraParticipantesVocabGuard. DOS NO TIENEN NINGUNA PROTECCION - ni FK, ni trigger, ni CHECK: terraPracticas.anestesiaType, que deberia resolver contra lookupOptions category terraAnestesia, y terraPracticasRegionesAnatomicas.regionAnatomicaValue, que deberia resolver contra category regionesAnatomicas. LO VERIFIQUE LEYENDO LOS TRIGGERS DE LAS DOS TABLAS, no la ausencia de un nombre. terraPracticas tiene 7 triggers no internos y son archive, assignPracticaNumber, firstRowMilestone, forbidHardDelete, forbidTruncate, practicaNumberImmutable y setUpdatedAt: numeracion, archivo y protecciones de borrado, ninguno mira vocabulario. terraPracticasRegionesAnatomicas tiene 6, todos de la familia detail - archive, archiveOnUpdate, archiveSoftDelete, forbidHardDelete, forbidTruncate, requireArchiveCtx - y ninguno valida el value. O sea que la ausencia esta medida, no inferida de que no exista un trigger llamado vocabGuard. POR QUE ES DEFECTO Y NO DISEÑO: porque tres de las cinco columnas comparables SI tienen guard. Si la decision fuera "el vocabulario se valida en la app", no habria tres triggers haciendolo en la base. La forma de la base dice que la regla es validar ahi, y estas dos quedaron afuera de esa regla sin que nada lo reporte. HOY NO HAY DAÑO Y LO DIGO CON DENOMINADOR PARA QUE NO SE LEA COMO ALARMA: anestesiaType resuelve en 6 de 6 filas, regionAnatomicaValue en 3 de 3. Cero valores no resolubles. Es exposicion, no incidente - y las poblaciones son chicas justamente porque la app recien empieza a escribir, que es cuando conviene poner el guard y no despues. LO QUE HABILITA HOY: un escritor de la app - o cualquier RPC - puede dejar un valor que ningun vocabulario reconoce, y no falla nada. Se descubre cuando un informe agrupa por ese valor y aparece una fila que no corresponde a ninguna opcion, o cuando un desplegable no puede mostrar lo que la fila guarda. MEDIDO POR db02-terra-cc Y RE-VERIFICADO POR MI contra pg_trigger y contra los datos. Su denominador: 17 tablas terra, 33 columnas text, poblacion definida por propiedad y no por lista de nombres. Una correccion menor a su reporte: regionesAnatomicas tiene 6 triggers, no 7. NO SE ESCRIBIO MIGRACION. terra_55 sigue sin GO en la eq 78 y las invariantes de esta clase esperan ese GO en vez de competir entre si.
  • wi-cli-venus completed · 5w ago
    DB guards applied and rollback-verified; browser/app exercise remains outside this DB closure.
task
6w ago by wi-cli-venus
5w ago
2026-09-10 14:43