Un log suelto
no dice nada.
El patrón está en la correlación.
Las líneas de Nazca son invisibles a nivel del suelo y solo revelan su forma desde arriba. Nazca hace lo mismo con la integración clínica: una traza no es un span HTTP, es un mensaje HL7v2 que entró por un canal Mirth, gatilló un procedimiento almacenado en la base de datos y salió por una API. Nazca los une en un solo árbol sin modificar a ningún emisor, y disocia los datos de paciente antes de que toquen el almacenamiento.
cargo deny limpio en las cuatro comprobacionesPor qué Nazca
Lo que Datadog no cubre en un hospital.
Correlación HL7 sin tocar al emisor
Un mensaje HL7v2 no lleva traceparent, y un HIS certificado no se instrumenta en una tarde. Nazca deriva el trace_id del MSH-10, que ya viaja dentro del mensaje. Cada participante calcula la misma función sobre el mismo dato y llega al mismo identificador, sin coordinación.
La disociación va antes, no después
El destino es un object store WORM: lo escrito no se borra. Disociar después de escribir no es una versión peor de disociar antes, es no disociar. Por eso el motor no es una etapa del pipeline, es la condición de entrada al almacenamiento.
Una sola tabla, no tres
Logs, spans y métricas comparten esquema columnar. Tres tablas separadas obligan a un join entre almacenes para responder lo que de verdad importa: este ADT que falló, qué hizo la base de datos en ese mismo milisegundo.
Multi-empresa por prefijo, no por WHERE
La organización es el primer segmento de la ruta en el object store y una columna no nula del esquema. Un fichero Parquet nunca contiene filas de dos empresas: el aislamiento no depende de que una consulta filtre bien, sino de que el dato nunca se mezcló.
El recorrido completo no existe
Toda consulta lleva ventana temporal. Sin last explícito se asume last 1h, y la ventana no es opcional en el plan: el full scan no está prohibido, es que no se puede escribir.
Sin inyección posible por construcción
El traductor MQL es un parser real sobre la gramática, no concatenación de cadenas. Todo nombre de campo se resuelve contra el esquema: un identificador que no esté ahí no produce un campo, y sin campo no hay predicado. El texto del usuario nunca forma parte de una sentencia.
El árbol dice qué dedujo y qué observó
Un enlace derivado del MSH-10 es una inferencia de Nazca, no algo que el emisor declaró. Cada nodo de la traza lo marca. Quien investiga un incidente a las tres de la mañana tiene derecho a saber qué parte del árbol se vio y cuál se dedujo.
El motor de base de datos da igual
Las columnas db_instance, db_namespace y db_statement_id son neutrales: en Oracle llevan el SID y el SQL_ID —cruzable contra AWR—, en PostgreSQL la base y el queryid de pg_stat_statements, y en SQL Server la instancia y el query_hash. La consulta se escribe igual en los tres.
El APM lo pone Cóndor
El ingress del stack ya observa cada petición HTTP: latencia, upstream, estado, reintentos. Exporta por OTLP y Nazca lo ingiere como a cualquier emisor, de modo que el span del proxy y el del canal HL7 caen en el mismo árbol. Nazca no reimplementa APM: lo correlaciona con lo clínico, que es lo que nadie más hace.
Soberano, en su propia infraestructura
Rust, sin control plane, sin servicio gestionado. El Parquet vive en OxideStore, las consultas las resuelve OxideDB y el tráfico entra por Cóndor. Los datos clínicos no salen del perímetro, que es la única forma de que la residencia de datos no sea una promesa.
El diferenciador
Tres emisores, ninguna propagación, una sola traza.
El MSH-10 ya viaja dentro del mensaje: todos los participantes lo conocen sin que nadie
se lo tenga que propagar. Si cada uno calcula sha256("hl7:" + MSH-10), los
tres llegan al mismo trace_id sin coordinarse y sin que nadie modifique al
emisor.
ADT^A31 del HIS. No habla OTLP; hace un POST con el mensaje crudo.hl7.control_id para entrar en la misma traza.# Un canal de integracion manda el mensaje crudo. Nazca hace el resto. POST /v1/events x-nazca-org: hospital-central x-nazca-project: his-prod { "signal": "span", "mirth_channel": "ADT_ENTRADA", "hl7_message": "MSH|^~\&|HIS_TEST|...|ADT^A31|MSG00001|T|2.5\rPID|1||11111111^^^HIS^MR||..." } # Extrae MSH-3, MSH-5, MSH-9, MSH-10 y MSH-7; seudonimiza el PID-3; # deriva el trace_id; y descarta el mensaje crudo. Nunca se almacena.
El estándar exige que el MSH-10 sea único por aplicación emisora, pero hay emisores que
reinician su contador. Dos episodios lejanos en el tiempo pueden compartir
trace_id; la ventana temporal obligatoria de toda consulta los mantiene
separados en la práctica.
Disociación
El dato persistido ya no es dato personal.
Ningún identificador directo de paciente se escribe jamás en el almacenamiento. La
seudonimización ocurre dentro del receptor, antes del buffer y antes del Parquet:
px_ + 24 hex de HMAC-SHA256(llave, dominio ‖ 0x1F ‖ valor), con
la llave fuera del almacén. Estable —el mismo paciente da siempre el mismo pseudónimo,
que es lo que permite seguir un episodio— y no reversible sin ella.
Lo que entra
MSH|^~\&|HIS_TEST|...|ADT^A31|MSG00001|T|2.5 EVN|A31|20260823143705 PID|1||11111111^^^HIS_TEST^MR||PACIENTE^DE^PRUEBA ||19800101|M|||CALLE FALSA 123^^SANTIAGO ||+56912345678 PV1|1|O|CONSULTA_TEST^^^HOSPITAL_TEST
Lo que se persiste
MSH|^~\&|HIS_TEST|...|ADT^A31|MSG00001|T|2.5 EVN|A31|20260823143705 PID|[segmento-disociado]|px_8d7002fd5560ee4380519ea4 PV1|1|O|CONSULTA_TEST^^^HOSPITAL_TEST # Lo técnico sobrevive: es lo que permite investigar. # Lo clínico y lo personal, no.
El barrido cubre RUT en sus tres formas —con puntos, con guion y desnudo, este último
validando el dígito verificador para no convertir cada identificador interno de nueve
dígitos en un pseudónimo—, correo, teléfono, segmentos PID| completos y
claves estructuradas anidadas, incluidas las que llevan prefijo
(x-patient-id, http.request.body.email). Con
service_env=prod y sin llave, el proceso no arranca.
MQL
Se lee de un vistazo o no sirve.
# Filtro por servicio y severidad service == "api-admisiones" and severity >= error | last 1h # Errores de ADT agrupados por canal Mirth hl7_message_type == "ADT^A31" and status == "ERROR" | last 24h | count by channel # Percentil 99 de latencia por span duration_ns > 5e9 | last 7d | percentile(duration_ns, 99) by span_name # La traza completa, en cascada trace: 3f9a2b1c4d5e6f708192a3b4c5d6e7f8 # La historia de un pseudónimo — exige rol elevado patient: px_a1b2c3d4e5f60718293a4b5c
org_id y project_id no se pueden filtrar desde MQL. No es que
fuera peligroso —la sesión acota igualmente los prefijos que se leen— sino que daría la
falsa impresión de que se eligen: escribir org_id == "otra-empresa" y
recibir cero resultados haría creer que esa empresa no tiene datos.
Comparativa
Nazca convive con Datadog, no lo reemplaza.
| Capacidad | Datadog | Elastic | Splunk | Nazca |
|---|---|---|---|---|
| APM y trazas de aplicación | ✓ | ~ parcial | ~ parcial | ✓ vía Cóndor |
| Ingress con trazas propias | ~ integración | ✗ | ✗ | ✓ Cóndor, mismo stack |
| Infraestructura y hosting | ✓ | ✓ | ✓ | ~ conviven |
| Parseo de HL7v2 | ✗ | ~ grok a mano | ~ regex a mano | ✓ nativo |
| Traza derivada del MSH-10 | ✗ | ✗ | ✗ | ✓ sin tocar al emisor |
| Correlación canal ↔ base de datos ↔ API | ✗ | ~ join manual | ~ join manual | ✓ un solo árbol |
| Cruce con el plan de la sentencia | ✗ | ✗ | ✗ | ✓ columna propia |
| Disociación PHI en el receptor | ~ scrubbing parcial | ~ ingest pipeline | ~ SEDCMD | ✓ antes del almacén |
| Pseudónimo estable y correlacionable | ✗ enmascara | ✗ enmascara | ✗ enmascara | ✓ HMAC por dominio |
| Vista de canal de integración | ✗ | ✗ | ✗ | ✓ volumen, error, atascos |
| Datos fuera del perímetro | ✗ SaaS | ~ según despliegue | ~ según despliegue | ✓ nunca salen |
| Almacenamiento WORM verificable | ✗ | ✗ | ~ frozen | ✓ Parquet en object store |
| Coste por volumen de logs | ✗ por GB ingerido | ~ por nodo | ✗ por GB ingerido | ✓ el de tu object store |
Comparación de alcance, no de madurez ni de benchmarks. El APM y las trazas de aplicación las cubre Cóndor, el ingress del mismo stack, que exporta por OTLP a Nazca: no es una carencia, es otra pieza. Donde Datadog sigue siendo mejor —infraestructura y hosting— conviven sin disputarse nada.
Estado
Qué funciona hoy.
Los componentes llevan nombre de geoglifo y el nombre describe la función: Colibrí recibe, Glifo graba, Pelícano consulta, Araña teje el grafo de spans, Garza espera inmóvil y ataca cuando algo se mueve, y Mirador es donde se mira. El motor de consulta se llamó Cóndor hasta que quedó claro que ese nombre ya estaba tomado por el ingress del stack.