Back to flin
flin

El opcode CreateEntity que desapareció

Cómo un manejador de opcode faltante en la máquina virtual de FLIN rompió silenciosamente toda la creación de entidades en funciones -- y el proceso de depuración que lo encontró.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 4 min flin
EN/ FR/ ES
flinrust

Hay una clase particular de error que te hace cuestionar la realidad. Todo parece correcto. El código compila. El servidor responde. No aparecen mensajes de error en ninguna parte. Sin embargo, algo fundamental está roto, y la única evidencia es una ausencia -- lo que esperabas que sucediera simplemente no sucede.

El 3 de febrero de 2026, encontramos uno de estos errores en FLIN. La creación de entidades dentro de funciones había dejado de funcionar silenciosamente. Los usuarios podían hacer clic en "Agregar tarea" en la aplicación de tareas y no pasaba nada. Sin error. Sin fallo. Sin advertencia. El botón se pulsaba, la solicitud se disparaba, el servidor respondía con {"type":"ok"}, y la lista de tareas permanecía sin cambios. El silencio era el síntoma.

La matriz de síntomas

La primera pista de que algo arquitectónico estaba mal vino del patrón de lo que funcionaba versus lo que no:

OperaciónEstadoNotas
saveEdit(task) -- editar entidades existentesFuncionandoCampos actualizados correctamente
toggleTask(task) -- modificar campos de entidadFuncionandoToggle booleano persistido
deleteTask(task) -- eliminar entidadesFuncionandoEntidades eliminadas correctamente
addTask() -- crear nuevas entidadesRotoNo pasaba nada en absoluto

Editar, alternar, eliminar -- todo funcionando. Solo la creación estaba rota. Y la creación era la única operación que requería construir un nuevo objeto de entidad desde cero.

El manejador faltante

En la Sesión 269, habíamos agregado varios manejadores de opcodes a execute_until_return para soportar el nuevo sistema de acciones. Cada opcode necesario para editar, eliminar y guardar entidades estaba presente. Pero CreateEntity -- el opcode que construye una nueva instancia de entidad -- no estaba en la lista. Había sido pasado por alto porque todas nuestras pruebas se enfocaban en saveEdit(task), que modifica entidades existentes usando SetField. Nunca probamos addTask() con el nuevo sistema de acciones.

Cómo funciona el fallo silencioso

Cuando la VM encontraba el bytecode para crear y guardar una nueva tarea:

ip=1742: CreateEntity (0x77) + u16 type_idx + u8 field_count = 4 bytes
ip=1746: StoreLocal (0x21) + u8 slot = 2 bytes
ip=1748: LoadLocal (0x20) + u8 slot = 2 bytes
ip=1750: Save (0x90) = 1 byte
ip=1751: LoadNone + Return

La VM no tenía manejador para CreateEntity en execute_until_return. El caso por defecto avanzaba el puntero de instrucciones solo un byte, pero CreateEntity es una instrucción de cuatro bytes. Así que el IP avanzaba al medio de los operandos de la instrucción. El siguiente byte se interpretaba como un opcode. Era basura, pero el caso por defecto lo manejaba avanzando de nuevo. El IP vagaba por el bytecode hasta que eventualmente golpeaba algo que causaba un retorno temprano.

La entidad nunca se creaba. El opcode Save nunca se alcanzaba. Sin error. Sin fallo. Solo silencio.

La corrección

La corrección fue sustancial pero mecánica -- agregar el manejador CreateEntity a execute_until_return:

rustOpCode::CreateEntity => {
    let type_idx = self.read_u16(code);
    let field_count = self.read_u8(code) as usize;
    let entity_type = self.get_identifier(chunk, type_idx)?;

    // Auto-register entity schema if not already registered
    if !self.database.has_entity_type(&entity_type) {
        use crate::database::EntitySchema;
        let schema = EntitySchema::new(&entity_type);
        let _ = self.database.register_entity(schema);
    }

    let mut entity = EntityInstance::new(entity_type.clone());

    for _ in 0..field_count {
        let value = self.pop()?;
        let name = self.pop()?;
        if let Value::Object(id) = name {
            if let Ok(s) = self.get_string(id) {
                entity.fields.insert(s.to_string(), value);
            }
        }
    }

    let id = self.alloc(HeapObject::new_entity(entity));
    self.push(Value::Object(id))?;
}

Lecciones para diseñadores de lenguajes

Este error ilustra varios principios que aplican a cualquier implementación de máquina virtual o intérprete.

Primero, los fallos silenciosos son la clase más peligrosa de errores. Un mensaje de error, por críptico que sea, al menos te dice que algo salió mal. Los fallos silenciosos no te dan nada.

Segundo, las tablas de despacho paralelas deben permanecer sincronizadas. Cualquier vez que tengas dos rutas de código que manejen el mismo conjunto de operaciones, agregar algo a una y olvidar la otra es una inevitabilidad.

Tercero, probar el flujo completo, no solo los componentes. Teníamos pruebas extensas para la creación de entidades. Teníamos pruebas para el sistema de acciones. Pero no teníamos una prueba que creara una entidad dentro de una función llamada por el sistema de acciones.


Esta es la Parte 156 de la serie "Cómo construimos FLIN", que documenta cómo un CEO en Abidjan y un CTO de IA diseñaron y construyeron un lenguaje de programación desde cero.

Navegación de la serie: - Arco anterior: biblioteca estándar y ecosistema de FLIN - [156] El opcode CreateEntity que desapareció (estás aquí) - [157] El error de iteración del bucle for

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

El segfault que no era nuestro: cómo lanzamos el tracking del día de lanzamiento de Déblo en la noche del despliegue — analítica condicionada por entorno, atribución nativa de las tiendas, tres bugs que el compilador no podía ver y un build sin memoria que diagnosticamos en lugar de revertir

El 1 de julio de 2026 — el día del lanzamiento — el riesgo nunca fue el texto. Era que las campañas de pago salieran a ciegas. Este es el build-log de cómo desplegamos la analítica y la atribución de instalaciones de Déblo como código en la noche del lanzamiento: etiquetas GA4, Meta y LinkedIn condicionadas por entorno que se despliegan sin riesgo antes de que existan las cuentas publicitarias; atribución enrutada por los canales nativos de las tiendas en lugar del pixel web; una auditoría adversarial que atrapó tres bugs que tanto el typechecker como el build dieron por buenos; y un despliegue en Easypanel que hizo segfault en el primer build — que demostramos que no era nuestro código antes de tocar una sola línea.

18 min Jul 1, 2026
deblolaunch-dayclaude-opus-4.8claude-code +26
Thales & Claude thales

Trece agentes, cuarenta y tres minutos: la primera sesión Workflow de Claude Fable 5, y lo que un script de orquestación determinista cambia en los builds multiagente

Un prompt, trece agentes, cuarenta y tres minutos: la primera sesión de producción con Claude Fable 5 y la herramienta Workflow de Claude Code entregó un sitio web de producción completo de siete páginas más un endpoint backend de captura de leads, en un solo commit. La bitácora: el script de orquestación determinista, el patrón de inyección de contrato entre fases, la economía por agente del fan-out paralelo, y el suspenso del límite de sesión que el diario de reanudación convirtió en un no-evento.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La puerta detectó su propia deriva: un día dentro de CASP con Claude Fable 5

Le entregamos al modelo Claude más autónomo hasta la fecha las llaves de CASP — la CLI open source que mantiene honestos a los agentes de código IA frente a git — con la autoridad de rechazar nuestra propia roadmap. Rechazó cinco cosas, encontró dos bugs reales en el validador al hacerle dogfooding, los corrigió bajo una puerta de dos auditores, y dejó casp check completamente en verde sobre su propio repositorio por primera vez. CASP 0.3.0 es el resultado.

15 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9