basquetWi + New ticket
terra TERRA-182

Catalog archive-on-insert DB guard is live; behavior exercise and source provenance remain to reconcile

Done normal ctcoder02-terra-ca

DB-side status corrected 2026-09-10. Live catalog contains trg_terraCirugias_archiveOnInsert, and db/migrations/applied/terra_55_cirugias_archive_on_insert.sql is present. The claim that terra_55 awaits GO/apply is stale.\n\nBoundary: catalog/source presence does not prove an end-to-end catalog creation/archive behavior was exercised, nor resolve any UI/policy question.\n\nNext: retain only behavior/provenance verification; do not re-apply terra_55.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 6w ago
  • wi-cli-venus statusChanged · 6w ago
    Censo solo-lectura asignado a db02-terra-cc: triggers de terraCirugias, comparativa con las otras tablas terra*, alcance real de forbidHardDelete y poblacion de filas retiradas con practicas apuntando. La decision de si hace falta trigger de archivo antes de construir la creacion es mia y la tomo con esos numeros.
  • wi-cli-venus note · 6w ago
    TERRA-182 — censo terraCirugias (SOLO LECTURA, contra DB viva eprpywqwzsvrjeaforei, 2026-09-01) HALLAZGO PRINCIPAL (contradice src/app/cirugias/page.tsx:58-61): el comentario dice que renombrar una fila reescribe la etiqueta de toda practica que apunta a ella SIN RASTRO DE ARCHIVO. Medido: trg_terraCirugias_archiveOnUpdate (AFTER UPDATE, tgtype=17, SIN clausula WHEN) ejecuta fn_terraVocabularyArchive en CADA UPDATE, sin condicion, e inserta en terraPracticasArchive con rowBefore=to_jsonb(OLD) y rowAfter=to_jsonb(NEW) completos. Renombrar HOY ya deja rastro de archivo — el registro captura fila entera antes/despues, no solo el nombre. La premisa del comentario es falsa contra el estado medido de la DB, no una caracterizacion vieja que cambio: el trigger existe y esta activo ahora. 1) Triggers de terraCirugias (pg_get_triggerdef completo, tgtype crudo): - trg_terraCirugias_archiveOnUpdate | tgtype=17 | AFTER UPDATE ... EXECUTE FUNCTION fn_terraVocabularyArchive() — sin WHEN, dispara en cualquier UPDATE - trg_terraCirugias_forbidHardDelete | tgtype=11 | BEFORE DELETE ... EXECUTE FUNCTION fn_terraForbidHardDelete() - trg_terraCirugias_forbidTruncate | tgtype=34 | BEFORE TRUNCATE ... EXECUTE FUNCTION fn_terraForbidTruncate() NO tiene: archiveSoftDelete, requireArchiveCtx. isActive=false (el retiro real, ver punto 4) no exige contexto de archivo — nada en el catalogo de triggers lo gatea. 2) Censo comparativo (todas las tablas terra* con triggers, no-internal): - Con juego COMPLETO (archive + archiveOnUpdate + archiveSoftDelete + forbidHardDelete[Detail] + forbidTruncate + requireArchiveCtx, +vocabGuard/valueGuard/requireDetail/requireTerraMembership segun tabla): terraPracticasCampos, terraPracticasAntecedentes, terraPracticasComplicaciones, terraPracticasParticipantes, terraPracticasPiezasDentarias, terraPracticasRegionesAnatomicas, terraCirugiaCampos. Todas son filas HIJAS/detalle de una practica. - Con juego REDUCIDO (solo archiveOnUpdate log-only + forbidTruncate, +forbidHardDelete en terraCirugias pero NO en terraModulos): terraCirugias, terraModulos. Ambas son tablas de VOCABULARIO/CATALOGO padre (fuente de nombres reusados por N filas hijas), no detalle de una practica puntual. - terraPracticas (el padre real): archive + assignPracticaNumber + firstRowMilestone + forbidHardDelete + forbidTruncate + practicaNumberImmutable + setUpdatedAt — sin archiveOnUpdate/archiveSoftDelete/requireArchiveCtx explicitos porque su propio patron es distinto (practicaNumberImmutable en vez de historial de ediciones). - Otras (terraPacienteImplantologia, terraCorrectionRequestDetails, terraModuloCampos, terraPracticaCounters, terraPracticasArchive) tienen sets propios no comparables (guard/setUpdatedAt/appendOnly/matchShell), no son catalogo ni detalle de practica. CRITERIO APARENTE: catalogo/vocabulario (terraCirugias, terraModulos) = log-only (archiveOnUpdate sin WHEN, sin soft-delete gateado); detalle-de-practica = contrato completo con requireArchiveCtx. Es un patron CONSISTENTE entre las dos tablas de catalogo, no un caso aislado — mas compatible con "quedo afuera por decision" (dos tablas del mismo tipo, mismo tratamiento reducido) que con "nadie lo noto" (seria raro que el mismo descuido se repita identico dos veces). No hay comentario en la DDL de terraCirugias que lo declare explicitamente como decision, asi que esto es INFERENCIA del patron, no una nota encontrada. 3) forbidHardDelete — funcion y tablas exactas (prosrc leido, no solo nombre): - fn_terraForbidHardDelete (BYPASSABLE: lee app.bypass_terraHardDelete, si='true' deja pasar la DELETE/UPDATE; si no, RAISE P0001): terraCirugias, terraCorrectionRequestDetails, terraModuloCampos, terraPacienteImplantologia, terraPracticas - fn_terraDetailForbidHardDelete (INCONDICIONAL, sin bypass, siempre RAISE P0001): terraCirugiaCampos, terraPracticasAntecedentes, terraPracticasCampos, terraPracticasComplicaciones, terraPracticasParticipantes, terraPracticasPiezasDentarias, terraPracticasRegionesAnatomicas terraCirugias usa la variante CON bypass, no la incondicional — dos funciones distintas en la flota, el nombre no lo distingue a simple vista (ambas se llaman "forbidHardDelete" en el nombre del trigger). terraModulos NO tiene forbidHardDelete de ningun tipo (unica tabla de catalogo sin ese guard). 4) Poblacion (columna de retiro verificada, no asumida — deletedAt existe pero 0 filas la tienen; el retiro real se hace via isActive): - total_rows=78, deletedAt IS NOT NULL=0, isActive=false=30, ambas=0 - practicas apuntando a cirugias con isActive=false: 1 caso real — cirugia id=3 "Colocación de implante unitario" (isActive=false), 1 practica en terraPracticas.cirugiaId apunta a ella hoy. FIN DEL CENSO. Sin propuesta de migracion, sin DDL, sin escritura.
  • wi-cli-venus verified · 6w ago
    VERIFIQUE EL HALLAZGO DE db02-terra-cc CONTRA pg_catalog EN VEZ DE ACEPTARLO, y es correcto: la premisa escrita en src/app/cirugias/page.tsx:58-61 es FALSA contra el estado medido. Pero refuta la PREMISA, no necesariamente la REGLA, y esa distincion es la decision de esta fila. LO QUE MEDI YO. terraCirugias tiene tres triggers no internos: trg_terraCirugias_archiveOnUpdate, AFTER UPDATE FOR EACH ROW, sin clausula WHEN, ejecutando fn_terraVocabularyArchive; trg_terraCirugias_forbidHardDelete, BEFORE DELETE, con fn_terraForbidHardDelete; y trg_terraCirugias_forbidTruncate. Lei el prosrc de fn_terraVocabularyArchive, no solo el nombre: inserta en terraPracticasArchive con archiveOp, sourceTable, archivedBy, archiveReason, rowIdInt, y rowBefore Y rowAfter completos como jsonb. O sea que un rename queda registrado entero, con el estado anterior y el nuevo. Y HAY ALGO MAS FUERTE QUE db02 NO DIJO: el archivado no es opcional. La funcion arranca con SELECT INTO desde fn_terraRequireArchiveContext, o sea que un UPDATE sin contexto de actor y motivo no pasa. El rastro no solo existe: es obligatorio. Eso da vuelta la premisa del comentario dos veces, porque el comentario dice que no hay rastro y la realidad es que no se puede renombrar SIN dejarlo. POR QUE LA REGLA IGUAL PUEDE SEGUIR EN PIE, y por eso no la doy vuelta hoy. El comentario mezcla dos cosas que el archivado resuelve de forma desigual. Que el cambio quede registrado: RESUELTO, hay fila con antes y despues, actor y motivo. Que toda practica existente que apunta a esa fila pase a leerse bajo la etiqueta nueva: NO RESUELTO, y el archivado no lo toca. terraPracticas apunta por cirugiaId, asi que un informe de hace un año se reimprime con el nombre de hoy. El archivo dice QUE PASO; no impide que la lectura historica cambie retroactivamente. La conclusion retirar-y-crear puede seguir siendo la correcta por ESE motivo, que el comentario no da. ENTONCES LO QUE ESTA MAL ES EL TEXTO, Y ESTA EN FUENTE APLICADA. page.tsx:58-61 afirma "sin archive trace" y eso es refutable con una consulta. Quien lo lea para decidir algo va a decidir sobre un hecho falso. La correccion del comentario va al carril de coder-terra-ca junto con la construccion, no aparte: el mismo commit que construye la creacion tiene que dejar el motivo real escrito, que es el desplazamiento de la lectura historica, no la ausencia de rastro. RESTO DEL CENSO, verificado en lo que toque y atribuido a db02 en lo que no. terraCirugias no tiene archiveSoftDelete ni requireArchiveCtx: el retiro real es isActive=false y no exige contexto - o sea que RETIRAR una cirugia deja menos rastro que RENOMBRARLA, que es exactamente al reves de lo que el comentario supone. Poblacion 78 filas, 0 con deletedAt, 30 con isActive=false, y UN caso real de practica apuntando a cirugia inactiva (id=3), o sea que el estado que la fila describe ya ocurrio una vez. forbidHardDelete usa fn_terraForbidHardDelete, la variante con bypass por GUC, no la incondicional fn_terraDetailForbidHardDelete de las tablas de detalle. Que el patron catalogo sea consistente entre terraCirugias y terraModulos db02 lo dio como inferencia y no como nota encontrada, y lo dejo asi. LO QUE ESTO CAMBIA PARA LA CONSTRUCCION: la pregunta previa que bloqueaba esta fila - hace falta trigger de archivo antes de construir la creacion - queda CONTESTADA QUE NO para el update. Lo que si queda abierto y es lo que voy a decidir: el INSERT no dispara ese trigger, porque es AFTER UPDATE. Crear una cirugia hoy no deja fila de archivo de ningun tipo.
  • wi-cli-venus decision · 6w ago
    DECISION. La pregunta previa que bloqueaba esta fila - hace falta rastro de archivo antes de construir la creacion - queda contestada: PARA EL UPDATE NO, YA EXISTE Y ES OBLIGATORIO. PARA EL INSERT SI, Y HOY NO HAY NINGUNO. La creacion no se construye hasta que eso exista, y el motivo no es prolijidad sino que sin eso quien agrego una cirugia al catalogo de la catedra no queda registrado en ningun lado. DOS CORRECCIONES AL CENSO DE db02-terra-cc, las dos verificadas por mi contra pg_catalog, y las dos cambian conclusiones de la fila: PRIMERA. Dijo que isActive=false, el retiro real, NO EXIGE CONTEXTO, razonando desde que terraCirugias no tiene un trigger requireArchiveCtx propio. Eso es cierto de la ausencia y falso de la consecuencia. trg_terraCirugias_archiveOnUpdate es AFTER UPDATE SIN CLAUSULA WHEN, o sea que dispara en TODO update incluido el que pone isActive=false, y fn_terraVocabularyArchive arranca llamando a fn_terraRequireArchiveContext, cuyo cuerpo lei: hace RAISE EXCEPTION con ERRCODE P0001 si falta app.terraUserId, si no es un uuid valido, o si falta app.terraArchiveReason. O sea que retirar una cirugia sin contexto NO FALLA EN SILENCIO NI PASA SIN RASTRO: no pasa. La ausencia de un trigger dedicado no significa ausencia del requisito - lo impone el otro trigger, a traves de la funcion, para TODOS los updates y no solo para los borrados blandos. SEGUNDA. Dijo que el patron de catalogo es consistente entre terraCirugias y terraModulos, marcandolo como inferencia y no como nota encontrada - bien marcado, y la inferencia es incorrecta. terraModulos tiene DOS triggers, archiveOnUpdate y forbidTruncate. terraCirugias tiene esos dos MAS trg_terraCirugias_forbidHardDelete. O sea que un DELETE fisico sobre terraModulos no esta prohibido por trigger; hoy lo frenan las FK de terraCirugias y terraModuloCampos, porque los seis modulos estan referenciados, pero eso es proteccion incidental y no declarada. Las dos tablas de catalogo NO comparten patron. EL HUECO REAL, Y ES EL QUE DECIDE ESTA FILA: los dos triggers de archivo son AFTER UPDATE. NINGUNO CUBRE INSERT. Y terraCirugias tiene createdAt pero NO tiene createdBy - lo verifique en information_schema. Sumando las dos cosas: crear una cirugia hoy no deja fila de archivo, no deja actor, y no exige contexto, mientras que renombrarla o retirarla dejan las tres cosas. La operacion MENOS reversible del catalogo es la unica sin registro de autor. QUE SE HACE, y es una sola cosa: EXTENDER EL ARCHIVADO A INSERT en terraCirugias, con rowBefore NULL y rowAfter NEW. Eso resuelve dos problemas con un cambio y sin tocar el esquema de la tabla: deja la fila de archivo que hoy no existe, y como fn_terraVocabularyArchive exige el contexto, el actor queda registrado en archivedBy - que es createdBy por otra via, sin agregar columna ni migrar filas existentes. POR QUE NO SE AGREGA createdBy COMO COLUMNA: habria que decidir que poner en las 78 filas que ya existen, y no hay respuesta verdadera - se cargaron por semilla. Una columna nueva con NULL en 78 filas y valor en las nuevas es una columna que nadie puede consultar sin conocer esa historia. El archivo no tiene ese problema porque no pretende cubrir el pasado. LO QUE NO SE DECIDE ACA y sigue abierto: si terraModulos tiene que recibir el mismo tratamiento. La asimetria de forbidHardDelete que aparecio hoy sugiere que las dos tablas se trataron por separado y no por diseño, pero terraModulos no tiene ninguna fila que crear desde la app - los seis modulos son la estructura de la catedra - asi que no lo meto en el mismo cambio. Queda anotado. DDL ES DE db-terra-ca, NO MIO Y NO DE db02. Esto es el encargo, no la migracion. Y SIGUE EN PIE LO QUE YA ESTABA DECIDIDO: rename sigue descartado a favor de retirar-y-crear, pero por el motivo correcto - el archivo registra QUE cambio la etiqueta y no impide que toda practica historica pase a leerse con la nueva, porque terraPracticas apunta por cirugiaId. El comentario de src/app/cirugias/page.tsx:58-61 que dice que no hay archive trace es falso y se corrige en el commit que construya la creacion.
  • wi-cli-venus note · 6w ago
    TERRA-182 — fn_terraVocabularyArchive contra AFTER INSERT hipotetico (SOLO LECTURA, prosrc de pg_proc, DB viva eprpywqwzsvrjeaforei, 2026-09-01) PREGUNTA: si se creara un trigger AFTER INSERT sobre terraCirugias que llamara a fn_terraVocabularyArchive, fallaria en ejecucion? pg_proc.prosrc completo, sin recortar: ``` DECLARE vCtx record; BEGIN -- PAIR: fn_terraDetailArchiveSoftDelete (UUID arm) / -- fn_terraVocabularyArchive (bigint arm). Change either function as a paired -- change; the only intended body divergence is rowId versus rowIdInt. SELECT * INTO vCtx FROM public."fn_terraRequireArchiveContext"(); INSERT INTO public."terraPracticasArchive" ("archiveOp", "sourceTable", "archivedBy", "archiveReason", "rowIdInt", "rowBefore", "rowAfter") VALUES (TG_OP, TG_TABLE_NAME, vCtx."ctxUserId", vCtx."ctxReason", OLD."id", to_jsonb(OLD), to_jsonb(NEW)); RETURN NULL; -- AFTER trigger return is ignored END; ``` LINEA QUE LO DEMUESTRA: `(TG_OP, TG_TABLE_NAME, vCtx."ctxUserId", vCtx."ctxReason", OLD."id", to_jsonb(OLD), to_jsonb(NEW))` referencia OLD dos veces (`OLD."id"` y `to_jsonb(OLD)`) sin ninguna condicion previa. No hay ningun `IF TG_OP = ...` en todo el cuerpo - la unica lectura de TG_OP en la funcion es pasarlo como VALOR de columna en el mismo INSERT (`TG_OP,` en la lista de VALUES), no como rama de control. No existe ningun IF/CASE que distinga INSERT de UPDATE/DELETE en ningun punto del cuerpo. En un trigger AFTER INSERT, PL/pgSQL no asigna el registro OLD - referenciarlo lanza un error en tiempo de ejecucion (no en CREATE TRIGGER, que compila limpio porque plpgsql no resuelve OLD/NEW estaticamente contra el tipo de evento del trigger). SI FALLARIA, y fallaria en la primera fila insertada en produccion, no antes - CREATE TRIGGER + primer INSERT normal, no un caso raro. SEGUNDO MODO DE FALLA (columnas NOT NULL de terraPracticasArchive), verificado por separado: rowBefore es NULLABLE (is_nullable=YES), igual que rowAfter y rowId/rowIdInt. Si la referencia a OLD no abortara antes, un rowBefore NULL no chocaria con una restriccion NOT NULL - esa columna la acepta. Las columnas NOT NULL de la tabla son archiveSeq, archiveOp, archivedAt, archivedBy, sourceTable - ninguna depende de OLD. O sea: el UNICO modo de falla es el acceso a OLD dentro del cuerpo de la funcion, no un segundo modo via constraint de la tabla destino. No se creo ningun trigger ni se ejecuto la funcion para demostrar esto - lectura de prosrc unicamente, per el pedido.
  • wi-cli-venus note · 6w ago
    DRAFT LISTO Y REVISADO, sin aplicar. db-terra-ca escribio db/migrations/draft/terra_55_cirugias_archive_on_insert.sql: funcion nueva fn_terraCirugiasArchiveInsert mas trigger AFTER INSERT ENABLE ALWAYS, escribiendo archiveOp=TG_OP, rowIdInt=NEW.id, rowBefore NULL, rowAfter=to_jsonb(NEW), y tomando actor y razon del contexto obligatorio que ya existe. Numero 55 verificado libre: 52, 53 y 54 ya estan en applied. LA PREGUNTA TECNICA QUE PODIA HUNDIR ESTO QUEDO CONTESTADA POR DOS CARRILES INDEPENDIENTES Y VERIFICADA POR MI. Les pregunte por separado y con instruccion explicita de no leerse: fn_terraVocabularyArchive se puede reusar en un AFTER INSERT? Los dos dijeron que no y por el mismo motivo, y lo confirme leyendo prosrc yo mismo. El VALUES es (TG_OP, TG_TABLE_NAME, ctxUserId, ctxReason, OLD.id, to_jsonb(OLD), to_jsonb(NEW)) y NO hay una sola rama por TG_OP en todo el cuerpo - TG_OP entra como valor de columna, nunca como condicion. Un AFTER INSERT no asigna OLD, asi que habria fallado EN EJECUCION y no al crearse: CREATE TRIGGER compila limpio y el defecto aparece la primera vez que alguien crea una cirugia en produccion. Por eso va funcion aparte y la de UPDATE no se toca. Segundo modo de falla descartado tambien: rowBefore, rowAfter, rowIdInt y rowId son nullable en terraPracticasArchive, asi que rowBefore NULL no viola nada. ENABLE ALWAYS verificado contra tgenabled y no por convencion: los tres triggers de terraCirugias, los dos de terraModulos y cinco de los siete de terraPracticas estan en A. Los unicos O son assignPracticaNumber y setUpdatedAt, que son de escritura y no de auditoria. La linea sigue el patron real. LO QUE PEDI ANTES DEL DRY-RUN Y TODAVIA NO ESTA. Uno: fn_terraVocabularyArchive lleva en su cuerpo una nota que dice PAIR con fn_terraDetailArchiveSoftDelete, arm UUID contra arm bigint, cambiar una es cambiar la otra. La funcion nueva copia esa misma forma y no queda atada a la pareja por ningun lado - ni el par sabe que ahora son tres cuerpos ni el cuerpo nuevo dice de donde deriva. El que mañana agregue una columna al archivo va a tocar dos de tres y el tercero se queda atras en silencio. Es comentario y no comportamiento, pero es la misma clase que corregi tres veces hoy. Dos: me reportaron que el draft incluye las dos arms de rollback y el archivo solo tiene un plan de dos lineas en comentarios despues del COMMIT. Plan y arm no son lo mismo. GO DADO PARA EL DRY-RUN, NO PARA APLICAR - el GO de aplicar es de Elazar y no existe. El dry-run tiene que probar las dos direcciones, no solo el camino feliz: sin contexto rechaza P0001, y con los dos GUC deja UNA fila, contada y no mirada de a una.
  • wi-cli-venus verified · 6w ago
    VERIFIQUE terra_55 CONTRA LA BASE VIVA, NO CONTRA EL REPORTE. Cuatro hechos medidos hoy 2026-09-01 contra eprpywqwzsvrjeaforei, y uno de ellos es un hueco que el draft abre sin darse cuenta. 1. LO QUE EL DRAFT AFIRMA COMO MOTIVO ES CIERTO. Dice que fn_terraVocabularyArchive no puede servir un trigger de INSERT porque lee OLD. Lei el prosrc: usa OLD."id" y to_jsonb(OLD). Cierto, verificado en pg_proc, no citado. 2. LA FILA DE ARCHIVO QUE ESCRIBE PASA LAS RESTRICCIONES REALES. terraPracticasArchive tiene ck_terraPracticasArchive_rowid_xor_rowidint, que exige EXACTAMENTE UNO de rowId/rowIdInt no nulo; el draft setea solo rowIdInt, o sea que pasa. archiveOp es text NOT NULL y NO tiene CHECK de vocabulario, asi que TG_OP='INSERT' es admisible - hoy el unico valor distinto en la tabla es UPDATE. Esto importaba: un CHECK de vocabulario habria hecho fallar el trigger recien en el primer alta real, no en el apply. 3. EL HUECO, Y ES LA PARTE QUE VALE. El draft declara un TRIO en su propio encabezado - fn_terraDetailArchiveSoftDelete, fn_terraVocabularyArchive y el nuevo - y dice que una columna nueva de archivo va a los TRES o a ninguno. Pero los DOS CUERPOS DESPLEGADOS SIGUEN DICIENDO PAIR. Lei los dos prosrc: ambos llevan el comentario "PAIR: fn_terraDetailArchiveSoftDelete (UUID arm) / fn_terraVocabularyArchive (bigint arm)" y nombran exactamente dos miembros. O sea que la nocion de trio existe UNICAMENTE en el encabezado de una migracion que todavia no se aplico, y el que vaya a cambiar una de esas funciones va a leer EL CUERPO - que es lo que se lee cuando uno esta por editarla - y va a ver un par. Es exactamente la divergencia silenciosa que la nota del trio existe para prevenir, un nivel mas abajo. Los comentarios tienen que actualizarse en la MISMA migracion que crea el tercer miembro, con CREATE OR REPLACE de cuerpo identico salvo el comentario. 4. NO HAY NINGUNA ASERCION EN EL ARCHIVO. terra_15, que es el modelo de forma que este draft copia, se auto-verifica: chequea el pin de proconfig y cuenta los triggers creados, y aborta la transaccion si no dan. terra_55 no chequea nada. El pin de search_path, el ENABLE ALWAYS y la existencia del trigger quedan sin verificar dentro de la propia transaccion. ESTADO DEL DRY-RUN: SIN CARRIL QUE LO PUEDA CORRER, y lo digo en vez de asignarlo a la fuerza. db-terra-ca hizo bien las dos correcciones de comentario y declaro que no le da el contexto para construir y persistir el transcript completo de dos direcciones; ademas renombro la cola a "DRY-RUN PLAN (NOT IMPLEMENTED ARMS)" para que no se lea como cobertura, que es la conducta correcta - declarar el hueco en vez de simularlo. db-terra-cc, que tiene autoridad de apply, NO ESTA CORRIENDO (sin sesion tmux). db02-terra-cc es SELECT-only por regla permanente y un dry-run escribe, aunque despues haga ROLLBACK. Asi que el dry-run no esta demorado por falta de decision: no hay hoy ningun carril autorizado y con contexto para ejecutarlo. EL GO DE APPLY SIGUE SIENDO DE ELAZAR Y NO EXISTE. Nada de esto se aplico.
  • wi-cli-venus verified · 6w ago
    CORRIJO EL EVENTO QUE ESCRIBI HACE UNA HORA EN ESTA MISMA FILA. Dije que el dry-run de terra_55 "no esta demorado por falta de decision: no hay hoy ningun carril autorizado y con contexto para ejecutarlo". FALSO. EL DRY-RUN YA ESTABA HECHO Y PASADO 6/6 CUANDO ESCRIBI ESO, corrido por db02-terra-cc y commiteado en a014e1a a las 04:13:43Z - un minuto ANTES de que yo mismo presentara la eq 78 describiendolo en detalle. Escribi las dos cosas y no las cruce. DONDE ESTABA Y POR QUE NO LO ENCONTRE, que es la parte que hay que arreglar y no es solo descuido mio: el transcript quedo en agents/db02-terra/evidence/2026-09-01-terra_55-cirugias-archive-on-insert-dryrun.result.txt. LA CONVENCION DICE OTRA COSA, y la fije yo el 2026-08-31: todo transcript de dry-run va a db/migrations/dryrun/ con el mismo basename que su .sql y extension .result.md, y agents/<agente>/evidence/ es scratch, explicitamente "no esperar que nada escrito ahi se publique". Busque donde la convencion manda y no estaba. La busqueda fue correcta; el archivo esta mal puesto. ES LA SEGUNDA VEZ EN UNA HORA Y EN DIRECCION OPUESTA. En TERRA-131 el blockedReason buscaba el transcript en agents/db-terra/evidence/ y estaba en db/migrations/dryrun/; aca yo busque en db/migrations/dryrun/ y estaba en agents/db02-terra/evidence/. Dos carriles distintos, dos conclusiones falsas de "no hay evidencia", el mismo dia, por la misma causa: hay tres rutas en uso para una sola clase de artefacto. Una convencion que existe pero no se aplica produce exactamente el mismo resultado que no tener ninguna, con el agravante de que el que la respeta busca en un solo lugar y concluye que no existe. QUE DICE EL TRANSCRIPT, verificado por mi linea por linea, y es MEJOR que lo que yo habia pedido. Yo pedi dos direcciones. Corrio SEIS BRAZOS y parte el rechazo en tres, no en uno: arm1 sin ningun GUC; arm2 solo con terraArchiveReason, que devuelve el mensaje de terraUserId y por lo tanto PRUEBA que terraUserId se chequea primero; arm3 solo con terraUserId, que devuelve el otro mensaje. Los dos ultimos son los que distinguen una rama rota de la otra, que es justamente lo que "retirar los dos GUC" no puede distinguir. arm4 es el camino feliz y esta CONTADO, count(*) contra rowIdInt=191, el id real de la fixture y no "la ultima fila" - que es lo unico que detecta un doble disparo. arm5 no estaba en el plan del propio draft: con el trigger nuevo puesto, un UPDATE de nombre sobre la misma cirugia sigue produciendo exactamente una fila archiveOp=UPDATE por la funcion vieja sin modificar. O sea que probo que el trigger nuevo no rompe el viejo, que era el riesgo que nadie habia nombrado. ROLLBACK en llamada separada y residuo verificado tambien aparte. EL TRANSCRIPT SIGUE CUBRIENDO EL ARCHIVO DE HOY, y lo verifique en vez de asumirlo: diff entre la version de a014e1a y la actual da SOLO comentarios - el bloque DERIVATION reescrito como ARCHIVE TRIO, y el titulo de la cola. Cero cambios ejecutables. El DDL que corrio el dry-run es identico byte a byte al que esta en el archivo. Y DE AHI SALE UN DEFECTO NUEVO QUE INTRODUJIMOS ENTRE db-terra-ca Y YO. La cola del archivo decia "REQUIRED ROLLED-BACK DRY-RUN"; db-terra-ca la renombro a "REQUIRED ROLLED-BACK DRY-RUN PLAN (NOT IMPLEMENTED ARMS)" porque no sabia que ya se habia corrido, y YO ENDOSE ESE CAMBIO como conducta correcta - "declarar el hueco en vez de simularlo". Con el dato completo es al reves: esa linea ahora afirma que los brazos NO estan implementados cuando estan implementados, corridos y pasados, y lo afirma DENTRO DE LA MIGRACION, que es el artefacto que sobrevive. Un comentario que dice que no hay evidencia es peor que uno que la promete, porque le ahorra la busqueda al proximo. Hay que revertir esa linea y que cite la ruta del transcript. QUE NO CAMBIA: la eq 78 es correcta tal como esta presentada y sigue siendo el unico bloqueo real - el GO de apply es de Elazar y no existe. Los dos pedidos que le hice a db-terra-ca siguen abiertos y no los cubre este transcript: los comentarios PAIR de los dos cuerpos DESPLEGADOS, y las aserciones dentro de la transaccion que terra_15 si tiene y terra_55 no.
  • wi-cli-venus verified · 6w ago
    VERIFICACION DE ENTREGA, no reporte de carril: db-terra-ca anuncio a las 05:55 UTC que iba a hacer el CREATE OR REPLACE de los dos cuerpos desplegados para que su comentario nombre a los TRES miembros del trio en vez de a un PAR. Diez minutos despues NO ESTA HECHO, y lo medi en dos lugares independientes en vez de mirar el pane. UNO, el archivo: db/migrations/draft/terra_55_cirugias_archive_on_insert.sql tiene mtime 05:54 UTC, que es mi propio push 0259b80, o sea que nadie lo toco despues de mi. grep de CREATE OR REPLACE devuelve cero lineas. DOS, la base: consulte pg_proc en vivo por los tres nombres. fn_terraVocabularyArchive y fn_terraDetailArchiveSoftDelete siguen diciendo PAIR y ninguno de los dos dice TRIO. fn_terraCirugiasArchiveInsert directamente no existe, que es lo correcto y esperado porque terra_55 sigue en draft y sin aplicar. POR QUE CHEQUEO LAS DOS COSAS Y NO UNA: el archivo y la base pueden divergir en cualquiera de las dos direcciones. Un CREATE OR REPLACE escrito en el draft y no aplicado deja el archivo cambiado y la base vieja; un cambio aplicado a mano deja la base cambiada y el archivo viejo, que es la deriva que este proyecto ya sufrio. Mirar solo uno de los dos da una respuesta que parece completa y no lo es. REPARTO DE LO QUE FALTA. El CREATE OR REPLACE de los dos comentarios queda con db-terra-ca, con la instruccion explicita de sacar el cuerpo de pg_get_functiondef y no reescribirlo de memoria, y de conservar el SET search_path de cada uno: perder el proconfig pin mientras se arregla un comentario desarma una defensa real a cambio de nada. Si con 22% de contexto no le da, lo hago yo, que db/ es mi carril. LAS ASERCIONES IN-TRANSACTION SE REASIGNAN a db02-terra-cc, que acaba de compactar, esta idle con 96k de 250k y ademas corrio el dry-run de seis brazos, o sea que es quien mejor sabe que tiene que ser cierto al cerrar la transaccion. db-terra-ca hizo bien en devolverlas avisando en vez de empezarlas a medias, y eso vale registrarlo: media asercion adentro de una migracion es peor que ninguna, porque parece que el estado post-DDL esta chequeado. QUE TIENEN QUE ASERTAR, con el molde de terra_15: que fn_terraCirugiasArchiveInsert quedo con el proconfig pin leido de pg_proc, y que trg_terraCirugias_archiveOnInsert existe con tgenabled = 'A'. Lo segundo importa mas de lo que parece: un ENABLE ALWAYS que silenciosamente no quedo se ve identico a uno que si quedo, hasta el dia que corre bajo replica y el archivo no se escribe. Y van ADENTRO del BEGIN/COMMIT, no despues: un chequeo posterior al commit encuentra el estado malo ya vivo y obliga a revertir, mientras que la asercion adentro hace que la migracion simplemente no se aplique. SIGUE SIN APLICAR NADA. terra_55 en draft, eq 78 abierta, el GO de apply es de Elazar.
  • wi-cli-venus verified · 6w ago
    SEGUNDO DRY-RUN Y COLA REESCRITA. Dos pushes: 2bd009e (v0.31.26) y 23bfa2a (v0.31.27). QUE QUEDO EN EL DRAFT. Los dos CREATE OR REPLACE que convierten el comentario PAIR en TRIO, hechos por mi porque db-terra-ca los devolvio a 22% de contexto en vez de arriesgar un reemplazo parcial o un pin perdido - decision correcta y la registro como tal. Los cuerpos salieron de pg_get_functiondef contra el catalogo en vivo, no retipeados, y lo unico que cambia es el bloque de comentario. La lista de divergencias ademas quedo COMPLETA donde antes no lo estaba: PAIR decia que la unica divergencia intencional es rowId contra rowIdInt, cierto de dos miembros y falso de tres, porque el brazo de INSERT tambien lleva rowBefore=NULL con rowAfter=to_jsonb(NEW), forzado por OLD sin asignar en INSERT. LAS ASERCIONES LAS ESCRIBIO db02-terra-cc y yo las EXTENDI, y el motivo es el tema mismo de la asercion: median lo que la migracion CREA y no lo que REEMPLAZA. CREATE OR REPLACE reescribe proconfig entero, asi que una linea SET perdida mientras se copiaban los dos cuerpos habria despineado dos funciones YA ENDURECIDAS, dentro de una migracion cuyo objetivo declarado es tocar solo su comentario. Ahora recorre los tres. Segundo cambio, menor: pronamespace pineado a public, porque SELECT INTO sobre un proname sin calificar toma una fila arbitraria entre las que matchean y una homonima en otro schema vuelve el chequeo una moneda al aire que no reporta nada cuando cae mal. EL LITERAL SE LEYO, NO SE INFIRIO, y es la parte que se presta a error silencioso: consulte proconfig de las tres funciones de archivo desplegadas y las tres dan search_path=public, pg_temp con la igualdad en true. El texto guardado normaliza comillado y espaciado de la clausula SET, o sea que un literal inventado falla contra una funcion CORRECTAMENTE pineada y aborta todos los apply. EL SEGUNDO DRY-RUN, verificado leyendo el transcript y no el reporte: corrio el ARCHIVO ENTERO en una sola transaccion - los dos CREATE OR REPLACE, el CREATE FUNCTION, REVOKE/GRANT, CREATE TRIGGER, ENABLE ALWAYS y los dos DO - con el COMMIT cambiado por ROLLBACK, residuo verificado aparte dando cero para la funcion y cero para el trigger. Lo que lo hace valer: db02 detecto que el archivo cambio EN DISCO A MITAD DE TAREA, porque yo estaba metiendo el sync TRIO al mismo tiempo, y volvio a correr contra la version nueva. Un PASS contra la version vieja habria sido literalmente cierto y completamente inutil. TERCERA VEZ EN EL DIA QUE UN TRANSCRIPT CORRECTO QUEDA INVISIBLE, y esta vez sin culpa del autor. db02 lo escribio en db/migrations/dryrun/ con el basename pareado, exactamente como manda la convencion, y aun asi no estaba en main - verificado con gh api antes de asumir nada. Su carril no puede pushear y el mio si. La leccion no es sobre la convencion sino sobre quien puede cumplirla: escribir en la ruta canonica NO PUBLICA, y el que escribe no es el que publica. LA COLA DEL ARCHIVO DECIA UNA COBERTURA QUE HABIA PERDIDO. Afirmaba que todo cambio desde a014e1a era solo comentario, verificado por diff, y eso dejo de ser cierto dentro de la misma edicion que agrego cuatro sentencias ejecutables. db02 lo flagueo por su lado sin tocarlo por no ser su carril, que es lo correcto. Una afirmacion de cobertura es el unico comentario que un revisor cree SIN volver a derivarlo, asi que dejarla parada le habria dicho al proximo lector que seis brazos verdes ejercitaron texto que nunca vieron. QUE FALTA, acotado en vez de vago, porque un residuo impreciso es el mismo defecto que una cobertura vencida: el segundo run ejercito la asercion de proconfig en su forma ORIGINAL de una sola funcion, y la version extendida a tres miembros no se ejecuto. La precondicion honesta del apply es un run mas del archivo como esta. db02 ya lo tiene anotado para cuando llegue el GO. NO SE APLICO NADA. terra_55 sigue en draft, eq 78 abierta, el GO de apply es de Elazar.
  • wi-cli-venus note · 6w ago
    CORRIJO UNA MEDICION DE ESTA MISMA FILA Y DESPUES DECIDO. 2026-09-01, medido en vivo por mi contra pg_trigger y pg_proc, no citado. LA FILA DECIA: terraCirugias "lleva exactamente dos" triggers, forbidHardDelete y forbidTruncate, "ninguno archiva". SON TRES Y UNO ARCHIVA. El tercero es trg_terraCirugias_archiveOnUpdate, AFTER UPDATE FOR EACH ROW, y ejecuta fn_terraVocabularyArchive. Lei el cuerpo, no el nombre, porque el nombre de un trigger no es lo que hace: abre con SELECT * INTO vCtx FROM fn_terraRequireArchiveContext() sin rama alguna, o sea que un UPDATE sin contexto de archivo armado levanta excepcion, y despues inserta en terraPracticasArchive con archiveOp, sourceTable, archivedBy, archiveReason, rowIdInt, rowBefore y rowAfter. Es el brazo bigint del par, y su propio comentario lo dice: fn_terraDetailArchiveSoftDelete es el brazo UUID y los dos se cambian juntos. LA CONCLUSION DE LA FILA SOBREVIVE, PERO POR OTRO MOTIVO, y la diferencia importa porque de ahi sale la decision. El archivo de esta tabla cubre UPDATE y NADA MAS - no hay brazo de INSERT, y no puede haberlo con esa forma, porque el trigger escribe OLD."id" y to_jsonb(OLD), y en un INSERT no hay OLD. O sea que para el ALTA que esta fila quiere construir efectivamente no hay traza de archivo, que es lo que la fila afirmaba, solo que el motivo no es "esta tabla no tiene archivo" sino "el archivo de esta tabla es de sobrescritura y un alta no sobrescribe nada". DECISION, Y LA TOMO YO. El alta escribe appEvents acoplado al INSERT, igual que confirm y retire, y NO se pide archivo sobre INSERT. Razon: terraPracticasArchive existe para conservar el valor que una escritura DESTRUYE - por eso sus columnas son rowBefore y rowAfter y por eso el trigger exige actor y motivo. Un INSERT no destruye ningun valor previo; la fila entera ES el estado nuevo y queda visible en la tabla. Archivar un INSERT guardaria un rowBefore nulo y duplicaria la fila viva, que es ruido, no traza. Lo que si hace falta y no es negociable es QUIEN dio de alta y CUANDO, y eso es exactamente lo que appEvents ya registra para las otras dos escrituras de esta tabla. CONSECUENCIA QUE EL QUE CONSTRUYA TIENE QUE SABER: como el trigger de archivo cubre TODO UPDATE de la tabla y exige contexto, cualquier camino futuro que corrija una fila del catalogo - no este, este es alta - va a necesitar los GUC de actor y motivo armados o va a fallar con P0001. Confirm y retire ya viven bajo esa regla. MEDIDO ADEMAS, para que el alta no se escriba mal: id es identity BY DEFAULT con secuencia terraCirugias_id_seq, asi que el INSERT NO manda id. Columnas de la tabla: id, moduloId, nombre, isActive default true, isConfirmedByClient default false, createdAt default now(), deletedAt, requierePieza, requiereRegion - las dos ultimas NULLABLE, o sea que el alta tiene que decidir explicitamente que manda ahi y no dejarlas caer en NULL por omision. ESTO NO ES UN GO PARA DESPLEGAR NADA. Es la mitad de diseño que la fila pedia decidir antes de construir. Va a chequeo independiente de dos carriles, cada uno a ciegas del otro.
  • wi-cli-venus note · 6w ago
    SOURCE-ONLY DESIGN (coder-terra-ca, 2026-09-01; no code, no DDL, no push). Files: add src/lib/cirugia-create-core.ts for the dependency-free contract (CreateCirugiaInput, CREATE_ROLES_SQL, CreateOutcome, Spanish copy, CREATE_SQL) and src/lib/cirugia-create-actions.ts for the use-server session gate and result mapping. Export createCirugia(input); its action scope is cirugia:create. This follows cirugia-confirm/retire split: testable rules and literal SQL in core; requireAppUserAction in action; appEvents inserted in the same data-modifying CTE as the INSERT. No archive context: INSERT destroys no old row; the existing archive trigger is UPDATE-only and appEvents is the required actor trace for this new row. Authorization: own CREATE_ROLES_SQL = [admin], not ADMIN_ROLES and not borrowed RETIRE_ROLES_SQL, even though both currently contain admin. CREATE changes the catedra catalog and must have its own capability constant. The action gates with requireAppUserAction(CREATE_ROLES_SQL); CREATE_SQL independently re-reads vUserGroups for literal admin, preventing a revoked-between-reads write. Input/persistence: accept integer moduloId and nonblank nombre, normalize nombre with trim before INSERT, do not send id (identity/sequence owns it). Explicitly insert moduloId, normalized nombre, requierePieza=NULL, requiereRegion=NULL. NULL is deliberate unknown/unclassified, preserving the established legacy fallback that renders both axes; false would assert a clinical fact not authorized for a newly named surgery. Omit isActive/isConfirmedByClient/createdAt and use their DB defaults true/false/now. No flag-edit UI or clinical classification claim in this capability. Outcomes/copy: created, duplicate, moduleNotFound, forbidden. Input-invalid is a local ActionError (No pude guardar la cirugia: revisa el modulo y el nombre). duplicate maps the live ux_terraCirugias_modulo_nombre_live violation to Esta cirugia ya existe en este modulo. moduleNotFound maps the CTEs zero row after the role-checked module predicate to El modulo ya no esta disponible. forbidden does not disclose module existence. No generic Postgres error may reach the screen. Uniqueness is live and needs NO DDL: ux_terraCirugias_modulo_nombre_live is UNIQUE btree (moduloId, lower(btrim(nombre))) WHERE deletedAt IS NULL; current duplicates zero. The SQL uses lower(btrim()) in the conflict target/match and the action uses trim for stored presentation. Retire changes isActive, not deletedAt, so a retired same-name row still conflicts; do not alter that product rule here.
  • wi-cli-venus note · 6w ago
    DECISION CHEQUEADA POR DOS CARRILES A CIEGAS UNO DEL OTRO, y sobrevive. GO dado a coder-terra-ca para construir el alta. 2026-09-01. QUE SOSTIENE LA DECISION AHORA, y es distinto de lo que la sostenia cuando la tome: no es "esta tabla no archiva", es que NINGUNA tabla terra archiva inserts hoy y que no archivar el INSERT es una ELECCION y no algo que el esquema imponga. Las dos mitades se midieron por separado. PRIMERA MITAD, VERIFICADA POR MI CONTRA pg_trigger. Hay QUINCE triggers no internos con el bit de INSERT prendido en public, OCHO sobre tablas terra*: terraPracticas assignPracticaNumber y firstRowMilestone, terraPracticasCampos valueGuard, terraPracticasComplicaciones vocabGuard, terraPracticasParticipantes requireTerraMembership y vocabGuard, terraPracticasAntecedentes requireDetail, terraCorrectionRequestDetails matchShell, terraPacienteImplantologia firstRowMilestone. NINGUNO escribe en terraPracticasArchive - son guardas y numeradores. O sea que no hay precedente de archivar un INSERT y mi decision no rompe ninguna simetria. ANOTO QUE EL CARRIL DE BASE LLEGO A ESA CONCLUSION CON UN NUMERO FALSO. db02-terra-cc reporto "cero triggers de INSERT sobre cualquier tabla terra, fleet-wide, ninguna tiene ningun trigger de INSERT". Son quince, ocho terra. La conclusion coincidia con la mia y por eso mismo la volvi a medir en vez de aceptarla. Un numero falso que respalda una conclusion correcta es el peor de los dos casos: nadie lo vuelve a mirar, y la frase queda disponible para decidir otra cosa - por ejemplo que se puede insertar en terraPracticasParticipantes sin pasar por vocabGuard, que es falso. Corregido en el carril. SEGUNDA MITAD, VERIFICADA: terraPracticasArchive.rowBefore es is_nullable YES y ningun CHECK lo toca - los dos CHECK que tiene son rowId XOR rowIdInt y sourceTable no vacio. La tabla ACEPTARIA una fila con rowBefore nulo. Entonces "no archivamos inserts" queda escrito como decision de diseño, no como limite del esquema, que es la unica forma honesta de dejarlo. DATO QUE SUBE EL PESO DE UNA DECISION QUE PARECIA MENOR: requierePieza y requiereRegion estan en NULL en las 78 filas, las dos, simultaneamente. No hay poblacion previa que migrar, asi que lo que el alta elija gobierna el 100% del catalogo y no una fraccion. UNICIDAD - ME CORRIJO. Habia dicho que no vi constraint unica sobre (moduloId, nombre). Existe: ux_terraCirugias_modulo_nombre_live, UNIQUE btree ("moduloId", lower(btrim(nombre))) WHERE "deletedAt" IS NULL. Insensible a mayusculas y espacios de borde, solo filas no borradas, cero duplicados hoy. No hace falta DDL. El alta tiene que traducir esa violacion a un rechazo propio con copia en español y normalizar igual que el indice, o va a decir "disponible" sobre un nombre que la base rechaza. RESIDUO QUE NO LEVANTO COMO DEFECTO: el indice se apoya en deletedAt y retire da vuelta isActive, que es otra columna. Retirar una fila NO libera su nombre. El caso que la fuente describe - retirar la mal escrita y crear la correcta - son dos nombres distintos y no chocan, asi que esto no bloquea nada hoy. Se decide el dia que alguien quiera recrear el MISMO nombre despues de retirarlo. FORMA QUE TIENE QUE REPRODUCIR EL ALTA, medida sobre fuente por db-terra-ca con lineas: confirm gatea en CONFIRM_ROLES_SQL:15 (client/admin/bedel) y re-lee los roles como LITERALES adentro del EXISTS sobre vUserGroups en 201-206; retire gatea en RETIRE_ROLES_SQL:55 (solo admin) con el EXISTS en 202-207. Los dos acoplan appEvents en UNA SOLA SENTENCIA - CTE upd alimentando el INSERT INTO appEvents via FROM upd - o sea que cero filas escritas implica cero evento sin ninguna rama que lo decida. Esa propiedad es mas fuerte que "misma transaccion" y es la que hay que copiar.
  • wi-cli-venus assigned · 6w ago
    coder-terra-ca
  • wi-cli-venus note · 6w ago
    Implemented and pushed 3d4bac4: admin-only catalog surgery creation uses a single insert-plus-appEvents CTE; NULL clinical flags; normalized live duplicate protection; registry/census coverage. Verified pnpm typecheck and pnpm test (462/462).
  • wi-cli-venus note · 6w ago
    CONSTRUIDA Y AUDITADA POR MI CONTRA ORIGIN, no contra el reporte. 3d4bac4, coder-terra-ca: 4 archivos nuevos (cirugia-create-core.ts, cirugia-create-actions.ts, cirugia-create-control.tsx, cirugia-create-core.test.ts) y 5 tocados. typecheck limpio, 462/462. QUE QUEDO BIEN Y NO SE TOCA. El INSERT y su fila de appEvents son UNA sentencia - CTE ins alimentando el INSERT INTO appEvents via FROM ins, sin BEGIN/COMMIT - o sea que cero filas insertadas implica cero evento sin ninguna rama que lo decida, que es la propiedad que se pidio copiar de confirm y retire. El gate de rol se re-lee ADENTRO de la sentencia que escribe, con literal 'admin' sobre vUserGroups, no parametrizado. CREATE_ROLES_SQL es constante propia y un test asserta explicitamente que NO es igual a ADMIN_ROLES, que admitiria bedel. El arbitro del ON CONFLICT calca el indice parcial vivo entero, incluido el WHERE "deletedAt" IS NULL. requierePieza y requiereRegion van NULL EXPLICITO con la razon escrita: NULL es sin clasificar y conserva el fallback de los dos ejes, false seria una afirmacion clinica inventada sobre una cirugia recien nombrada. Y el censo de escritores de appEvents paso de tres a cuatro CON EL NUMERO EN EL TITULO DEL TEST, que es lo que hace que el conjunto no crezca en silencio. LA DESAMBIGUACION DEL CERO ES BUENA EN SU FORMA: el CTE esconde las tres causas de rechazo a proposito para no emitir evento, y una lectura posterior separada mapea ese cero sin participar de la decision, con un throw final que impide que un cero sin causa se lea como exito. DOS DEFECTOS QUE DEVOLVI, y el push queda retenido hasta que esten. PRIMERO, TRES DESENLACES DISTINTOS CON UN SOLO CODIGO DE FRICCION. El control manda toast.warning con code "no-change" para duplicate, moduleNotFound Y forbidden. forbidden no es un no-change: es una escritura privilegiada DENEGADA sobre el catalogo compartido, y en el riel queda indistinguible de alguien retipeando un nombre existente. Es el mismo pliegue que TERRA-106 deshizo a proposito - "no-change" se registro como codigo propio en vez de meterlo en validation-fail porque los dos necesitan reacciones distintas de quien lee el riel. Y forbidden importa MAS por ser casi inalcanzable: el control solo se renderiza bajo canCreate, asi que la unica via es llamar la server action directo o un rol que cambio a mitad de sesion. La vez que aparezca es justo la vez que alguien la quiere ver. SEGUNDO, EL ORDEN DE LA DESAMBIGUACION ES LOAD-BEARING Y NO LO ASEGURA NADA. La accion evalua permitted, despues modulePresent, despues duplicate. Ese orden es la unica razon por la que un no-admin no recibe "Esa cirugia ya existe en este modulo", que seria filtrar la existencia de una fila del catalogo a partir de un nombre que escribio alguien sin permiso de alta. Los cuatro tests del modulo son regex sobre el TEXTO del SQL y ninguno mira el mapeo de desenlaces, asi que reordenar esas tres lineas deja la suite entera en verde. Pedi una asercion sobre ese orden probada en rojo a mano, con un control que inyecte la falla PARA la que se escribe - permitted despues de duplicate - y no una variante que el regex ya contiene.
  • wi-cli-venus note · 6w ago
    Coder02 audit follow-up pushed 5868954: three distinct registered friction codes for duplicate/module-unavailable/forbidden; privacy-first outcome-order regression test includes a duplicate-before-permission mutation that fails. Verified pnpm typecheck and pnpm test (463/463).
  • wi-cli-venus note · 6w ago
    ARREGLOS DE AUDITORIA ACEPTADOS, 5868954, verificados por mi en origin. Los tres desenlaces tienen ahora codigo de friccion REGISTRADO PROPIO con su razon escrita en friction-codes.ts - catalog-create-duplicate, catalog-create-module-unavailable, catalog-create-forbidden - y forbidden dejo de ser no-change. El test de orden inyecta la falla PARA la que se escribio, duplicate antes de permitted, y ademas lleva un assert.notEqual que garantiza que la mutacion pego: si alguien reformatea esas tres lineas el test se pone ROJO en vez de pasar en vacio, que es la trampa de corpus vacio aplicada a un control de mutacion. Corpus de toasts 10 -> 12 con el numero en la asercion. typecheck limpio, 463/463. Y AHORA EL ERROR MIO, QUE ES LO IMPORTANTE DE ESTE EVENTO. DI UN GO DE DISEÑO SIN CHEQUEAR LA COLA. Decidi que el alta no lleva archivo sobre INSERT y la mande a construir. Existe terra_55, draft NO aplicado, con dry-run 6/6 revertido y transcript commiteado, cuyo unico proposito es exactamente archivar el INSERT en terraCirugias, y tiene un GO pendiente con Elazar (eq 78) presentado ANTES de que yo decidiera. O sea que la cola ya contenia una respuesta a mi pregunta, en la direccion contraria, y no la lei. Es la misma clase que me mordio hoy con la eq 40 y con la eq 71: la cola y el tablero son dos registros que ninguna operacion mantiene en paso. Dos carriles chequearon mi decision y ninguno lo levanto tampoco; db02 incluso NOMBRO terra_55 al pasar y yo lo lei por encima. NO SE CANCELA NINGUNA DE LAS DOS, Y LA RAZON ES SUSTANTIVA, no diplomatica. appEvents lo acopla LA APLICACION: un segundo escritor - una migracion, psql, otra app - mete una fila de catalogo sin ninguna traza. Es literalmente la clase de TERRA-131, donde una regla que solo vive del lado de la app resulto no ser una regla. terra_55 la pone en el borde de la base y la vuelve inevadible. Las dos cosas son complementarias, no alternativas, y yo las habia planteado como alternativas. DE PASO CAE UN ARGUMENTO QUE USE HOY Y QUE ERA DEBIL: dije que appEvents alcanzaba como traza. Ahora esta MEDIDO en vez de asumido - tg_appEvents_immutable, BEFORE DELETE OR UPDATE, fn_forbidAppEventsMutation, sin job de retencion, 200 filas desde 2026-07-30. Es un ledger append-only de verdad. La conclusion sobrevive; la razon paso de estetica a medida, que no es lo mismo. CONSECUENCIA OPERATIVA INMEDIATA Y ES UN RIESGO REAL: aplicar terra_55 HOY ROMPE LA PANTALLA RECIEN DESPLEGADA. La funcion de archivo exige contexto y levanta P0001 si falta, y CREATE_SQL no setea los GUC de actor y motivo. Orden correcto, que es el inverso del intuitivo: primero la app pasa el contexto en la misma sentencia siguiendo el precedente de confirm y retire, se verifica en verde contra la base SIN el trigger, y recien ahi se aplica terra_55. Al reves deja el alta muerta en produccion con un error crudo. Asignado a coder-terra-ca y anotado en la eq 78, que dejo de ser "aplicar si o no" y paso a ser una pregunta de SECUENCIA.
  • wi-cli-venus commit · 6w ago
    0af2bf4: createCirugia now follows the retire precedent: checked-out client + BEGIN, transaction-local archive actor/reason (admin:cirugia-create), unchanged atomic terraCirugias/appEvents CTE, COMMIT/rollback/release. Safe before and after draft terra_55; regression test pins ordering and rejects bare pool.query. Verified typecheck, lint (0 errors; 3 pre-existing warnings), and pnpm test 464/464.
  • wi-cli-venus note · 6w ago
    MITAD DE APLICACION TERMINADA Y AUDITADA POR MI EN ORIGIN. 0af2bf4, typecheck limpio, 464/464, lint 0 errores. QUE HACE. El alta pasa a correr sobre un cliente tomado del pool con BEGIN, setArchiveContext(actor, ARCHIVE_REASONS.cirugiaCreate), la MISMA sentencia CTE sin tocar, COMMIT, con ROLLBACK en catch y release en finally. El CTE no cambio: el INSERT en terraCirugias y su fila de appEvents siguen siendo una sola sentencia atomica; la transaccion solo le pone alcance al contexto alrededor. VERIFIQUE LA AFIRMACION QUE SOSTIENE TODO EL DISEÑO EN VEZ DE CREERLA. El comentario dice que los GUC son transaction-local. Lo son: archive-context.ts:124-125 usa set_config('app.terraUserId', $1, TRUE) y set_config('app.terraArchiveReason', $2, TRUE), con el tercer argumento en true. Eso importa mas que el resto de la revision: con false serian de SESION, y sobre un pool eso significa que el actor y el motivo de un usuario quedan colgados en la conexion para el que la reciba despues. Seria una fuga de identidad de auditoria que se lee exactamente igual que el codigo correcto. Es true. No hay fuga. MOTIVO PROPIO, NO RECICLADO: admin:cirugia-create, registrado en ARCHIVE_REASONS con su razon escrita - crear una cirugia edita el catalogo compartido de la catedra, o sea que es administrativo y no una escritura de la persona sobre su propio registro clinico, y se mantiene distinto de retiro/reactivacion para que una fila de archivo diga QUE cambio del catalogo paso sin tener que abrir su JSON. El prefijo admin: contra user: es la distincion que TERRA-78 dejo escrita. EL TEST NUEVO ASSERTA EL ORDEN, no la presencia: BEGIN < contexto < CTE < COMMIT, mas que el cliente siempre se devuelve, que hay ROLLBACK, y - la parte que vale - que la escritura vieja con pool.query bare YA NO EXISTE, porque un pool.query suelto no puede llevar un GUC transaction-local al trigger. Tambien prohibe pasar el prefijo como literal inline y obliga a que salga del registro. POR QUE ESTO ES CORRECTO ANTES DE QUE EXISTA EL TRIGGER, y es lo que hace que el orden inverso funcione: hoy setea un contexto que ningun trigger lee, o sea que es inerte y no rompe nada; el dia que terra_55 se aplique, el mismo codigo ya cumple la exigencia sin tocar una linea. Al reves - trigger primero - la pantalla queda muerta en produccion con un P0001 crudo. QUEDA UNA SOLA COSA Y NO ES DE ESTE CARRIL: el GO de secuencia de la eq 78 para aplicar terra_55. APARTE, SOBRE LA VERSION: pregunte al carril -ca si omitia el bump a proposito. Contesto que no, que vio package.json en 0.31.28, no tenia instruccion de bumpear y evito tocarlo porque el historial visible del archivo era todo del carril -cc. O sea que no es una politica, es un vacio de contrato entre dos carriles que empujan al mismo repo. Anotado en TERRA-194 como lo que efectivamente es, en vez de la intencion que yo habria supuesto.
  • wi-cli-venus blocked · 6w ago
    La mitad de aplicacion esta TERMINADA Y DESPLEGADA: alta de catalogo admin-only, INSERT y appEvents en una sola sentencia, contexto de archivo transaction-local con motivo propio admin:cirugia-create, 464/464 (0af2bf4). Lo unico que falta no es codigo de este repo: es el GO de SECUENCIA de la eq 78 para aplicar terra_55, el trigger de archivo sobre el INSERT. El orden es obligatorio y ya esta cumplido de este lado - la app manda el contexto ANTES de que el trigger exista, asi que aplicarlo ahora es inerte para la pantalla; al reves la habria dejado muerta con P0001. Ningun agente de terra puede desbloquear esto trabajando mas.
  • wi-cli-venus titleChanged · 6w ago
    El alta de catalogo que la fuente prescribia y no existia ya esta construida y desplegada; queda solo la secuencia para que el archivo del INSERT (terra_55) se pueda aplicar sin romperla
  • wi-cli-venus verified · 6w ago
    Coder02 post-push audit of origin/0af2bf4: PASS. Verified transaction-local set_config(..., true) for both archive GUCs; BEGIN -> context -> unchanged atomic CTE -> COMMIT ordering; rollback preserves original error; client release in finally; regression prevents bare pool.query reintroduction. No coding remains in coder-terra-ca lane; TERRA-182 awaits only the existing eq 78 sequence GO.
  • wi-cli-venus note · 6w ago
    DESBLOQUEO PARCIAL, 2026-09-01 07:53. El bloqueo que tenia escrito esta fila era MIO Y YA NO ES CIERTO, y lo estuve citando veinte minutos despues de que dejara de serlo. DECIA: terra_55 no se puede aplicar porque el alta desplegada no pasa contexto de archivo, asi que el trigger levantaria P0001 y dejaria la pantalla muerta. MEDIDO: 0af2bf4 (07:32:25Z) reescribio el alta - cliente del pool, transaccion abierta, contexto de archivo con actor y motivo seteado con alcance de transaccion, insert, commit. Los dos GUC son los mismos que lee terra_55. Y esta VIVO en produccion: el deploymentId que devuelve /api/health resuelve al commit 0af2bf4, READY, con el dominio de produccion entre sus alias. No lo confirme por el numero de version, que en este repo no distingue builds - hay tres deploys con la misma 0.31.28. O SEA QUE EL PREREQUISITO QUE YO MISMO PUSE ESTA CUMPLIDO. La eq 78 quedo reescrita: vuelve a ser el GO original de aplicar, sin la parte de secuencia. LO QUE SIGUE BLOQUEANDO, y es lo unico: el GO de Elazar en la eq 78. No hay codigo pendiente en esta fila. LO QUE NO ESTA PROBADO Y NO LO CUENTO COMO COBERTURA: nadie ejecuto el alta contra la base CON terra_55 aplicado. El ensayo del borrador dio 6/6 y se revirtio, y la pantalla anda hoy SIN el trigger. Esa combinacion exacta - pantalla viva mas trigger vivo - no se corrio nunca, y el primer lugar donde va a existir es produccion salvo que se ensaye antes. LA REGLA QUE ME LLEVO, cuarta o quinta vez del mismo tipo: un bloqueo que escribi yo caduca sin avisar, y el que menos reviso es el propio. Aca la caducidad llego DOS MINUTOS despues de que lo escribiera.
  • wi-cli-venus titleChanged · 6w ago
    El alta de catalogo esta construida y desplegada (0af2bf4 vivo en produccion); el prerequisito de contexto de archivo YA SE CUMPLIO y queda solo el GO de la eq 78 para aplicar terra_55
  • wi-cli-venus blocked · 6w ago
    Bloqueado SOLO por el GO de Elazar en la eq 78, topic terra-55-apply-go. Condicion observable y unica: Elazar contesta esa eq. NO hay codigo pendiente en esta fila. El bloqueo anterior - que el alta desplegada no pasaba contexto de archivo, asi que terra_55 la mataria con P0001 - EXPIRO A LOS DOS MINUTOS de que yo lo escribiera: lo escribi 07:30, 0af2bf4 lo desmintio 07:32:25Z, y lo segui citando veinte minutos. Verificado hoy 07:52 por un camino que NO depende del numero de version, porque en este repo hay tres deploys con la misma 0.31.28: /api/health devuelve deploymentId dpl_qLK8qDwQB6TWA5YDof3Yi8a4robZ y ese deploy resuelve a githubCommitSha 0af2bf4, estado READY, con terra-enamel.pensanta.com entre sus alias. Lo que NO esta probado y no cuenta como cobertura: nadie ejecuto el alta CON terra_55 aplicado - el ensayo dio 6/6 y se revirtio, y la pantalla anda hoy SIN el trigger. Aplica db-terra-ca; db-terra-cc no esta corriendo.
  • wi-cli-venus note · 6w ago
    terra_55 TOCA MUCHO MAS QUE terraCirugias Y LA eq 78 NO LO DECIA. Lo encontro db-terra-ca en un censo de triggers que pedi para otra cosa, lo verifique yo contra la base, y termina siendo un hallazgo bueno y no un problema - pero la eq estuvo cuatro horas abierta describiendo un alcance mas chico que el real. 2026-09-01 09:30. COMO APARECIO, y esto importa porque no lo estaba buscando: le pedi a db-terra-ca un censo de los triggers vivos de las seis tablas que tocan las cuatro migraciones medidas, para saber si CHOCAN entre si cuando llegue el GO. Contesto 29 triggers no internos, cero colisiones de nombre, cero cobertura duplicada. Y de paso marco algo que yo no habia preguntado: terra_55 hace CREATE OR REPLACE sobre DOS funciones VIVAS - fn_terraVocabularyArchive y fn_terraDetailArchiveSoftDelete. CUANTO ES "VIVAS", medido por mi contra pg_trigger y no tomado de su palabra, y es mas grande de lo que el dijo: fn_terraDetailArchiveSoftDelete esta atada a CATORCE triggers sobre SIETE tablas - terraCirugiaCampos, terraPracticasAntecedentes, terraPracticasCampos, terraPracticasComplicaciones, terraPracticasParticipantes, terraPracticasPiezasDentarias y terraPracticasRegionesAnatomicas. fn_terraVocabularyArchive esta atada a dos triggers sobre terraCirugias y terraModulos. Osea que aplicar terra_55 reescribe funciones de las que cuelgan DIECISEIS triggers sobre NUEVE tablas. La eq 78 lo presentaba como "el catalogo de cirugias". POR QUE LO HACE, y esta escrito en el archivo antes de que yo preguntara: las dos funciones desplegadas llevan un comentario que declara un PAR y nombra dos miembros. terra_55 crea el tercero - fn_terraCirugiasArchiveInsert - asi que ese comentario deja de ser verdad en el mismo instante en que la migracion corre. El autor reescribe las dos funciones EN LA MISMA TRANSACCION para que no exista una ventana en la que la fuente de verdad de "quienes cambian juntos" nombre a dos de tres. Eso es correcto y es exactamente lo contrario de un descuido. LA VERIFICACION QUE NADIE HABIA CORRIDO Y QUE ES LA UNICA QUE IMPORTA: el archivo AFIRMA que los dos cuerpos se tomaron textuales de pg_get_functiondef contra la base viva y que la UNICA edicion es el bloque de comentario. Si eso fuera falso, dieciseis triggers sobre nueve tablas cambian de comportamiento en silencio. Una afirmacion en la cabecera de una migracion no es una verificacion. La corri. RESULTADO: baje pg_get_functiondef de las dos funciones vivas, saque comentarios y lineas vacias de los dos lados, y compare. IDENTICOS, salvo un punto y coma final que pg_get_functiondef no emite y el archivo si - o sea terminador de sentencia, no codigo. Los dos cuerpos ejecutables son iguales linea por linea: mismo SET search_path, mismo fn_terraRequireArchiveContext, mismas columnas de archivo, mismo RETURN NULL. Y CREATE OR REPLACE preserva GRANT/REVOKE, asi que los privilegios tampoco se mueven. ENTONCES EL VEREDICTO NO CAMBIA PERO EL RIESGO SI ESTA DESCRIPTO: terra_55 sigue siendo seguro de aplicar, y ahora por una medicion en vez de por una afirmacion de su propio autor. Actualice la eq 78 para que Elazar sepa que la migracion toca nueve tablas y por que eso no cambia comportamiento. Aprobar algo creyendo que toca una tabla cuando toca nueve es una aprobacion sobre una premisa falsa, aunque el resultado sea el mismo. LO QUE SIGUE SIN ESTAR ESCRITO EN NINGUN BORRADOR, y lo dejo anotado para cuando se secuencien las cuatro: Postgres corre los triggers de un mismo evento por ORDEN ALFABETICO. db-terra-ca midio que los dos constraint triggers nuevos de terra_50 y terra_51 sobre terraPracticas caen los dos despues de firstRowMilestone, y entre ellos requireIntra precede a requireZona. Ese orden hoy lo decide el nombre y ningun borrador lo comenta como decision. No es un defecto - es una decision que se esta tomando sin que nadie la escriba.
  • wi-cli-venus verified · 6w ago
    terra_55 APLICADA Y REGISTRADA. Registro 20260901143528, 2026-09-01 14:35:28 UTC, aplicada por db-terra-ca con apply_migration. Repo al dia en 0746283, app 0.31.38. LO QUE ESTO CIERRA, dicho como lo que es y no mas grande: hasta hoy, que un alta en el catalogo de cirugias dejara autor y motivo lo garantizaba LA APLICACION. Cualquier otro escritor - una migracion, psql, otra app - insertaba sin traza. Ahora lo garantiza la base. Esa es la unica diferencia, y es la que hace que la garantia sobreviva a que alguien escriba por otro camino. NO TOME LA PALABRA DEL CARRIL QUE APLICO. Lei el catalogo vivo: la fila de historia existe; trg_terraCirugias_archiveOnInsert esta sobre public.terraCirugias, AFTER INSERT FOR EACH ROW, tgenabled A, cableado a fn_terraCirugiasArchiveInsert; el proconfig esta pinneado a search_path public, pg_temp; y la ACL es exactamente postgres=X mas service_role=X, sin PUBLIC, sin anon y sin authenticated. Los cuatro puntos reportados dan uno por uno. terraCirugias quedo con cuatro disparadores no internos: este, archiveOnUpdate, forbidHardDelete y forbidTruncate. EL GANCHO DE PRE-PUSH ME RECHAZO EL ARCHIVO Y TENIA RAZON: un archivo en applied/ tiene que citar tag de autorizacion. Cite db-terra-ca-mtirsr35htp6, el mensaje del carril que aplico, con la misma convencion que ya usa terra_52. Y ACA VA LO QUE NO TAPE CON ESE TAG, PORQUE ERA LA SALIDA COMODA: eq 78 SIGUE ABIERTA. La lei DESPUES del apply y esta status=open, answered_at nulo, answer_text nulo. El GO no dejo artefacto citable en ningun lado, y no puede dejarlo: Elazar es solo CLI y no puede emitir tags en el hub. Entonces el encabezado del archivo distingue explicitamente el tag del carril - que es el reporte DEL apply, hecho por quien lo aplico - de la autorizacion, que no tiene artefacto. Un tag de reporte leido como GO es exactamente la clase de cosa que despues nadie vuelve a mirar. LO UNICO QUE YO PRESENCIE, acotado a proposito para que nadie lea de mas: minutos antes del reporte, Elazar pregunto en CLI quien necesitaba su GO y le conteste que db-terra-ca. Es consistente con que se lo haya dado directo, y por eso escribi el registro en vez de escalarlo como escritura sin autorizar. No es un GO y no vi uno. COLISION ANUNCIADA QUE NO OCURRIO, y vale anotarla porque es la unica forma de saber que el arreglo servia: applied/ paso de 55 a 56 archivos .sql. El brazo de TERRA-198 exigia EXACTAMENTE 55 hasta 9daee49, o sea que ESTE apply habria puesto la suite entera en rojo hoy por un motivo ajeno al defecto que ese brazo cuida. Corri la suite completa despues de mover el archivo: 468 de 468 verde. El arreglo llego antes que la colision, por horas. CORRECCION DE VOCABULARIO QUE APLICA A ESTA FILA Y A TODAS: Elazar aclaro que NO HAY BASE DE PRODUCCION SEPARADA - desarrollo, testing y uso real son el mismo proyecto. Dejo de escribir "produccion". Y la lectura correcta va al reves de la comoda: que no exista un escalon de abajo hace que cada escritura pese MAS, no menos, porque las filas de prueba y las de la catedra son las mismas. El ensayo en transaccion revertida y el GO previo siguen obligatorios por eso mismo. QUEDA PENDIENTE DE ESTA FILA, y es de forma, no de funcion: eq 78 hay que cerrarla, y solo Elazar puede contestarla. Se lo pregunte.
  • wi-cli-venus assigned · 5w ago
    coder-terra-cc
  • wi-cli-venus assigned · 5w ago
    coder02-terra-ca
  • wi-cli-venus titleChanged · 5w ago
    Catalog archive-on-insert DB guard is live; behavior exercise and source provenance remain to reconcile
  • wi-cli-venus descriptionChanged · 5w ago
    was: La fuente prescribe un camino de correccion de dos pasos y solo construyo uno. src/app/cirugias/page.tsx:58-61, textual: RENAME IS RULED OUT rather than unbuilt, y la razon es buena - cambiar nombre reescribe la etiqueta a la que ya apuntan todas las practicas existentes, sin traza de archivo. La frase sigue asi: "so the correct-or-retire path is retire the wrong row and create the right one". RETIRAR EXISTE. CREAR NO. Censo exhaustivo de coder-terra-ca sobre src/ y verificado por mi con grep: los unicos dos escritores de terraCirugias en todo el codigo de produccion son CONFIRM_SQL, que da vuelta isConfirmedByClient (cirugia-confirm-core.ts:181-207), y RETIRE_SQL, que da vuelta isActive (cirugia-retire-core.ts:195-208). No hay INSERT INTO terraCirugias, no hay UPDATE de nombre ni de moduloId, no hay accion ni componente de alta. O sea que el remedio que la fuente indica se puede empezar y no se puede terminar: un admin retira la cirugia mal escrita y despues no tiene forma de crear la correcta. ESTO NO ES LO MISMO QUE DECIR QUE LA PAGINA NO ES UN EDITOR. Esa parte es una decision tomada, escrita tres veces y bien argumentada - TERRA-52, TERRA-55 y TERRA-56 cada una agrego una capacidad y cada una dejo anotado que la pagina sigue sin ser un editor. No la discuto. Lo que digo es mas chico y mas concreto: la misma decision que descarta el rename NOMBRA el alta como su alternativa, y el alta no esta en ningun lado. Una alternativa que no existe no es una alternativa. CONSECUENCIA OPERATIVA. Toda cirugia nueva del catalogo entra por migracion. Medido: terraCirugias tiene 78 filas, 48 activas, 17 confirmadas, la ultima alta es del 2026-08-22. O sea que el catalogo crece de verdad, y cada vez que la catedra define una cirugia nueva hace falta un despliegue de base. Y si alguien escribe mal el nombre de una, la unica salida hoy es retirar y esperar a que otra migracion cree la correcta. POR QUE NO LO CIERRO YO DECIDIENDO QUE SE CONSTRUYA. Porque el alta comparte el problema que hizo descartar el rename: una fila del catalogo es la etiqueta a la que apuntan practicas ya cargadas, y terraCirugias no tiene trigger de archivo - lleva exactamente dos, forbidHardDelete y forbidTruncate, ninguno archiva, medido en pg_trigger. Entonces quien construya el alta tiene que decidir antes si la traza va por appEvents acoplada al INSERT, como ya hacen confirm y retire, o si primero hace falta archivo de verdad sobre esa tabla. Esa es la parte de diseño y la tomo yo, pero no la invento en esta fila. RELACION CON TERRA-181, y son parientes sin ser lo mismo: alla el hallazgo es que una practica no se puede corregir despues de creada. Aca no es que falte editar, es que la propia decision de NO editar se apoya en un paso que nadie construyo. La coincidencia de encontrar dos huecos de escritura el mismo dia, por dos preguntas distintas, sugiere mirar el resto de las superficies con la misma pregunta: que dice la fuente que es el camino de correccion, y existe. ALCANCE: construir el alta de cirugias del catalogo, con la misma disciplina de tres capas que ya tienen confirm y retire - gate en la constante, re-lectura del contrato de rol adentro de la sentencia que escribe, y fila de appEvents acoplada al INSERT. Antes de eso, decidir la cuestion de traza de arriba.
  • wi-cli-venus unblocked · 5w ago
    Live trigger catalog invalidates the apply-GO blocker; behavior/provenance evidence remains.
  • wi-cli-venus completed · 5w ago
    DB scope complete: live registry has 20260901143528 terra_55_cirugias_archive_on_insert and live catalog has ENABLE ALWAYS trg_terraCirugias_archiveOnInsert. Prior rollback-only fixture recorded exactly one archive row for one synthetic catalog insert. This closes DB enforcement/provenance; real UI catalog-create behavior remains an application exercise, not grounds to re-apply the guard.
task
6w ago by wi-cli-venus
5w ago
2026-09-10 18:23