Expediente 1/1852: el generador que informó éxito

Una clave repetida, cuarenta y dos ficheros que son uno solo, y una portada sin una sola palabra — mientras el proceso que la mató salía con código 0

Jesús Pérez
Cambié una frase en la fuente y regeneré la proyección, como manda el protocolo. La portada apareció vacía: cajas con geometría y sin palabras. El generador había terminado con código 0 y escrito «spine generated». El fichero llevaba la frase nueva, correcta, en la línea 2 — y repetía una clave en la 31. Fluent no elige entre dos claves iguales: descarta el recurso. Y el recurso son los cuarenta y dos ficheros del idioma concatenados en uno. Una clave, mil ochocientas cincuenta y dos perdidas. El contrato de este protocolo caza cualquier dato mal tipado al definirlo, y suelta la mano exactamente donde el dato se convierte en texto.
Expediente 1/1852: el generador que informó éxito

🕵️ Mostrar expediente completo → 📋 Protocolo de sesión →

Expediente · Depto. de Homicidios de Código

Cambié una frase en la fuente, regeneré la proyección como manda el protocolo, y la portada apareció vacía: los titulares sin texto, los botones sin texto, las tarjetas sin texto. Cajas con geometría y sin palabras. El generador había terminado con código 0 y había escrito «spine generated» en la consola. No mintió sobre lo que hizo: escribió el fichero, y el fichero llevaba la frase nueva, correcta, en la línea 2. Lo que nadie comprobó —ni él ni yo— fue la línea 31, donde repetía una clave que ya había escrito en la 30. Fluent no perdona eso: no elige una, no ignora la otra. Descarta el recurso. Y el recurso, en este framework, son los cuarenta y dos ficheros del idioma concatenados en uno.

Caso Nº 1/1852Clasificación: ANTI-PAP · EL CONTRATO QUE ACABA DONDE EMPIEZA EL TEXTOEstado: RESUELTO
Mostrar glosario
proyección
Un artefacto generado desde una fuente declarada. La regla es de una sola dirección: la deriva se arregla re-ejecutando el generador, jamás editando la copia servida (ADR-057). Todo el sitio está construido así — y es exactamente por eso que un generador que miente contamina río abajo sin que nadie pueda detenerlo aguas arriba.
contrato
En Nickel, un predicado que se aplica a un campo y rechaza el fichero al exportarlo. Su virtud es que no se puede ignorar: o el dato pasa, o no hay dato. Y su frontera es este expediente: el contrato vigila el dato mientras es dato. En el instante en que un generador lo convierte en TEXTO, el contrato suelta la mano.
recurso Fluent
El cargador concatena los 42 ficheros .ftl de un idioma en UNA sola cadena y lo analiza como un único recurso. No hay espacios de nombres. Una clave repetida no gana ni pierde: invalida el recurso, y el recurso es el idioma entero.
clave inyectiva
Un esquema de nombres es inyectivo cuando dos entradas distintas no pueden producir el mismo nombre. `spine-hook-` nombra por PUERTA una lista indexada por HOOK. Dos hooks en la misma puerta y la inyectividad se rompe — en silencio, porque nada en el proceso la exigía.

Protocolo para declarar, versionar y verificar esto → ontoref.dev

El doble balance — lo que costó, y lo que dejó

Las cifras de la izquierda son el cuerpo. La de la derecha, la lección. Y conviene mirar de frente la proporción que preside el expediente: una clave, mil ochocientas cincuenta y dos perdidas. No es un error proporcional a su causa. Es un fusible al revés: el componente más pequeño del sistema tiene autoridad para apagarlo entero, y hasta hoy nada le impedía ejercerla.

Lo que costó el crimen

  • Ficheros .ftl que el cargador concatena en UN solo recurso (locale es) 42
  • Claves de ese recurso 1852
  • Claves del fichero que el generador rompió 29
  • Claves duplicadas necesarias para perder las 1852 1
  • Palabras que quedaron en la portada 0
  • Código de salida del generador que la dejó así 0 · «spine generated»
  • Hooks apuntando a la puerta developer (el esquema de clave admitía uno) 2
  • Línea en la que la página de aterrizaje YA desambiguaba esa puerta 93
  • Línea en la que la emisión del .ftl la duplicaba 179
  • Distancia entre las dos, en el mismo fichero 86 líneas
  • Puertas que ejecuta just check en este sitio 8
  • …que comprueben que un .ftl es válido 0
  • Lo que verifiqué antes de cantar victoria el fichero
  • Lo que no verifiqué la página

Lo que el caso dejó

  • La regla un generador que corre no es un generador que funciona
  • El cerrojo el generador se NIEGA a escribir claves repetidas, y falla el build
  • La desambiguación primero-gana por puerta — la misma regla que la otra superficie ya usaba
  • El mensaje del cerrojo dice que el recurso se caería entero y dejaría la página en blanco
  • El oráculo, que ya estaba escrito «verifica la TARJETA renderizada, no un número en el HTML» (adr-072)
  • El detector, comprobado se le inyectó un duplicado y lo cazó

El punto de abandono — lo que no sale en la tabla

El punto de abandono no estuvo al final, estuvo al principio, y lo crucé sin verlo. Cuando la primera regeneración terminó, hice lo que parecía riguroso: abrí el .ftl y comprobé que la frase nueva estaba ahí, correcta. Estaba. spine-hero = Pisa firme, con paso ligero., línea 2, exactamente lo pedido. Di el paso por bueno y seguí. Verifiqué el fichero, no la página — y el howto de esta misma serie lo tiene escrito, en mayúsculas, desde otro expediente que costó lo suyo: «verifica la TARJETA renderizada, no un número en el HTML; un check que mide un proxy de la capacidad es un check que puede estar en verde mientras la página está mal». Lo tenía delante. No lo leí, porque no lo pregunté: cogí un fichero de ejemplo, deduje la forma, y me puse a trabajar. La comprobación que me habría salvado costaba un curl.

Los sospechosos — las pistas falsas

El CSS del overlay“«Es el CSS del overlay que acabas de meter.» Fue la primera hipótesis, y la más natural: la página PARECÍA un render roto, con cajas y sin texto, justo después de tocar el CSS del hero. Se descarta mirando de cerca: las cajas tenían su geometría intacta, los bordes, los radios, el fondo. No era que el texto estuviera invisible. Es que no había texto. Un fallo de estilo deja palabras mal pintadas; éste no había dejado ninguna.”descartado — el estilo no borra palabras, las pinta mal
El fichero .ftl, mal escrito“«El .ftl está mal escrito.» Lo abrí y decía, en la línea 2, exactamente lo que debía decir: `spine-hero = Pisa firme, con paso ligero.` Correcto. Verificado. Y ahí estuvo mi error, que es el de este expediente: comprobé el artefacto que me importaba y lo encontré bien. El fichero era correcto en la línea 2 y letal en la línea 31, y yo había dejado de leer en la 2.”descartado — y aquí está mi confesión
«Fluent ignora la clave repetida y sigue»“«Fluent ignorará la clave repetida y seguirá.» Es lo que uno espera de un formato de traducciones: última gana, o primera gana, y a otra cosa. Se descarta leyendo el cargador: `bundle.add_resource(resource).map_err(...)?`. El signo de interrogación es todo el caso. Una clave repetida no degrada: aborta. Y como los cuarenta y dos ficheros del idioma se concatenan en UN solo recurso, aborta el idioma entero. Fluent no hizo nada malo. Falló en cerrado, que es lo correcto — y no había nadie escuchando.”descartado — Fluent hizo lo correcto
«Si el generador sale en 0, el artefacto es válido»“«Si el generador sale en 0, el artefacto es válido.» CULPABLE. Y no es de nadie: es la creencia por defecto. El proceso escribió su fichero, imprimió «spine generated», devolvió 0, y lo que dejó en el disco era un recurso que su consumidor rechaza entero. El generador nunca supo que hacía algo malo, porque nada le había dicho jamás qué era hacerlo bien. Todo el protocolo está construido sobre contratos que rechazan el dato mal tipado en el instante de definirlo — y ese contrato suelta la mano justo cuando el dato se convierte en texto. A partir de esa frontera, se publica bajo palabra.”CULPABLE — y es la creencia de todos

El arma — gen-spine-pages.nu — una clave que nombra la puerta, un bucle que recorre los hooks, 86 líneas entre ambos

# gen-spine-pages.nu — línea 93. La página de aterrizaje de la puerta:

  let hook = ($spine.evidence_hooks | where {|h| $h.door == $d.key })
  let h = ($hook | first)          # ← una puerta, un hook. Desambiguado.

# ... 86 líneas más abajo. La emisión del .ftl:

  let hook_keys = (
    $spine.evidence_hooks
    | each {|h| $"spine-hook-($h.door) = ($hl)" }   # ← itera por HOOK
    | str join "\n"                                  #   nombra por PUERTA
  )

# spine.ncl declara DOS hooks con door = "developer".
# La misma lista. El mismo fichero. Dos lecturas incompatibles.
#
# `spine-hero = Pisa firme, con paso ligero.`   ← línea 2, correcta
# `spine-hook-developer = ...`                   ← línea 30
# `spine-hook-developer = ...`                   ← línea 31, y el idioma ha muerto

Y el arma llevaba meses cargada sin que nadie la tocara. El .ftl que había en disco se generó cuando sólo existía UN hook; el segundo entró después y el fichero no se volvió a regenerar. La bomba estaba armada, esperando la próxima ejecución del generador — que resultó ser la mía, por cambiar una frase. Yo no creé este bug. Lo desperté. Y ésa es la peor propiedad de un artefacto generado que nadie regenera: no está estable, está caducando en silencio, y el que pase por ahí se lo lleva puesto.

El giro — Agrupar por lo que la clave nombra — y ponerle al generador la puerta de salida que no tenía

# 1. La clave nombra la PUERTA, así que se agrupa por puerta. Primero-gana:
#    la misma regla que la página de aterrizaje aplicaba 86 líneas más arriba.

  | group-by door
  | items {|door, hs| let h = ($hs | first); $"spine-hook-($door) = ..." }

# 2. Y el cerrojo, que es lo único que impide que esto VUELVA a ocurrir:
#    el generador valida lo que va a escribir, ANTES de escribirlo.

  let dup = ($ftl | lines | ... | where n > 1 | get key)
  if ($dup | is-not-empty) {
    error make { msg: $"home_spine.ftl [($lang)] llevaría claves Fluent duplicadas —
      el recurso se caería entero y dejaría la página en blanco: ($dup)" }
  }
  $ftl | save -f $destino     # ← ya no se llega aquí con basura

# El contrato ya no acaba donde empieza el texto.

El cerrojo se falsó antes de darlo por bueno: se le inyectó un duplicado y lo cazó. Es una prueba pequeña y es la única que importa, porque la lección de este expediente es precisamente que un mecanismo no comprobado es una intención. Habría sido perfectamente coherente escribir el cerrojo, verlo pasar en verde sobre un fichero sano, y publicarlo — que es exactamente lo que hace el detector defectuoso del expediente 0/318.

El veredicto

La serie pregunta: ¿con ontoref no hubiera sido así? Y este caso responde con una incomodidad distinta a la de los anteriores, porque aquí el protocolo no falló por ausencia: falló por frontera. Todo lo que este proyecto declara está tipado, contratado y rechazado en el instante de definirlo. nickel export no deja pasar un dato mal formado; el esquema del expediente que estás leyendo rechaza un caso al que le falte un campo; los contratos fallan en definición, no en tiempo de ejecución — y eso está escrito, literalmente, en el diagrama de arquitectura de la portada. Todo cierto. Y todo se detiene exactamente en el punto donde un script toma ese dato impecable y lo escribe como texto. A partir de ahí no hay contrato, no hay tipo, no hay puerta: hay una cadena, un save -f, y un exit 0. La proyección es la práctica central del sitio —la deriva se arregla re-ejecutando el generador, nunca editando la copia servida— y esa práctica descansa entera sobre la confianza en que el generador produce algo válido. Nadie lo comprobaba. Lo que hace de esto un caso y no una anécdota es que el fallo no fue silencioso ni gradual: detonó. Una sola clave repetida, en un fichero de veintinueve, dentro de un idioma de mil ochocientas cincuenta y dos, y la portada se quedó sin una palabra. No degradó. No perdió el hero. No mostró la clave entre corchetes, como hacían los dieciocho títulos rotos que encontramos esa misma tarde. Se apagó. Y el proceso que la apagó terminó bien, informó éxito, y devolvió cero — porque nadie le había dicho nunca qué era hacerlo mal.

Un generador que puede escribir un artefacto inválidoSe le pone una puerta de salida. Ahora valida lo que va a escribir antes de escribirlo, y si fuera a repetir una clave, ABORTA el build diciendo por qué. Un generador sin contrato de salida no es un generador: es una sugerencia con permisos de escritura.
Un esquema de clave no inyectivo sobre su entradaSe agrupa por lo que la clave NOMBRA, no por lo que el bucle itera. La clave decía «puerta» y el bucle recorría «hooks». Cuando el nombre y el índice no coinciden, la colisión no es un riesgo: es una cuestión de tiempo.
El contrato acaba donde el dato se vuelve textoEl contrato se lleva hasta el generador. Nickel rechaza cualquier dato mal tipado al exportarlo — y suelta la mano en el instante en que un script convierte ese dato en una cadena. Todo lo que se escribe después de esa frontera se publicaba bajo palabra. Ahora, no.
Verificar el fichero en vez de la páginaEl howto de esta misma serie ya lo dice, y no lo leí: «verifica la TARJETA renderizada, no un número en el HTML». Comprobé que el .ftl decía la frase correcta en la línea 2 y di el trabajo por bueno. Era cierto. Y era irrelevante: el fichero moría en la línea 31.
Un idioma entero en un solo recurso, sin espacios de nombresEsta fragilidad no se repara desde aquí — vive en el cargador del framework, y ahí add_resource(...)? propaga el error y se lleva las 1852 claves. Se CONTIENE con lo anterior: si nadie puede emitir un duplicado, la fragilidad deja de ser alcanzable. Contener no es curar, y el expediente lo dice en vez de disimularlo.
Un bug latente que solo despierta al regenerarEl .ftl en disco se había generado cuando sólo existía un hook. El segundo hook entró después, y el fichero no se volvió a generar: la bomba estaba armada y esperaba a la próxima ejecución. Un artefacto generado que nadie regenera no está estable — está caducando en silencio.

La jurisprudencia — qué exige hoy la lección

  • La página es la publicación: el testigo pregunta lo que preguntaría un lectoradr:072/the-page-is-the-publication
  • Una puerta mide la capacidad, no un proxy — el fichero era el proxy de la páginaadr:072/a-check-measures-the-capability-not-a-proxy

Deuda declarada: La frontera sigue abierta donde el dato se vuelve texto: lo que se escribe tras ella se publica bajo palabra. Lo saldaría un contrato sobre el artefacto EMITIDO —un esquema del recurso generado—, no sólo sobre su fuente.

Queda una cosa por decir, y es la que ordena a este expediente junto a los otros de la tarde. En la misma sesión aparecieron un diagrama que afirmaba en su propio pie ser una proyección viva mientras sus cifras estaban tecleadas a mano; dieciocho rutas sirviendo el nombre de una clave rota como <title>, bajo un contrato de publicación que declaraba «witnessed 0 brackets»; y la superficie de español más grande del sitio, ciento cincuenta y cinco ficheros, fuera de toda política. Los tres son el mismo defecto que este caso, en su versión lenta: un artefacto que afirma algo sobre sí mismo y nadie le pide la prueba. La diferencia es que aquellos derivaban en silencio durante meses y éste detonó en un segundo. Y por eso este es el que se publica: porque la deriva silenciosa se puede discutir, y una portada en blanco no se discute con nadie.

Glosario

¿Te ha resultado útil? Valóralo
¿Tienes algo que aportar? Cuéntame qué opinas, qué sugieres, o si seguimos explorando este tema.
· lecturas

Usamos cookies para que este sitio funcione, entender el uso del servicio y apoyar acciones de marketing. Política de cookies para más información.