Expediente 404-PAP: la deriva hardcoded
Cómo una tarde perdida bajo una deriva anti-PAP se arregló en 8 líneas — y se convirtió en sistema
🕵️ Mostrar expediente completo → 📋 Protocolo de sesión →
Expediente · Depto. de Homicidios de Código
Era una tarde tranquila. Entonces <span class='mono'>/recursos</span> entró por la puerta y no mostró nada. Nada de nada. Y yo, ingenuo, pensé que sería rápido.
Mostrar glosario
- PAP
- Project's Architecture Principles — las reglas y patrones que sostienen la arquitectura: la fuente única de la verdad que todo el código debe respetar.
- anti-PAP
- Código escrito en contra de esas reglas. Aquí: un stub que re-lista a mano lo que routes.ncl ya declara.
Protocolo para declarar, versionar y verificar esto → ontoref.dev
El doble balance — lo que costó, y lo que dejó
Un caso no se juzga solo por lo que cuesta. Se juzga por lo que deja. De un punto des‑esperado —ocho pistas falsas, seis fingerprints, la tarde entera— recobramos el rumbo, y lo que venía en el mismo viaje encajó de golpe:
Lo que costó el crimen
- Caza pura del origen (solo framework) ~88 <i>min</i>
- Builds de release lanzados 8
- Reinicios del servidor ~15+
- Fingerprints de build coexistiendo 6
- Ficheros de código <i>ajeno</i> leídos (2 repos) ~20+
- <span class='mono'>cargo clean</span> completos (867 MB al garete) 2
- Pistas falsas antes de la buena 8
- Tamaño del arreglo final 8 <i>líneas</i>
Lo que el caso dejó
- Fix registry-driven PAP-compliant
- Conocimiento content-kind-howto
- Mecanismo modo generate-expediente
- Evidencia proof (positioning)
- Serie + hub /expedientes
- Framework desatascado
Esa es la profundidad creíble: la tarde de dolor no terminó en un parche, terminó en sistema declarado. Ontología, modos y posicionamiento no se “añadieron” — estaban viajando juntos y el caso los hizo converger.
El punto de abandono — lo que no sale en la tabla
Hay un punto en toda deriva que ninguna tabla registra: aquel en que dejas de tener miedo y empiezas a tener ganas de abandonar. Los bugs se vuelven fantasmas; todo se para y no sabes por qué; ya no hay urgencia, solo cansancio. ‘Es demasiado, no se puede resolver.’ Con ontoref ese punto no debería llegar: el dao te hace nombrar la tensión en vez de colapsarla, los modos te dan el siguiente paso, los ADRs cierran lo ya decidido. La deriva no te hunde porque el mapa te sostiene.
Los sospechosos — las pistas falsas
| El contenido en site/r | “Yo no estaba sincronizado.” | cómplice menor |
| filename ≠ id | “El body salía vacío, pero no fui yo el del 404.” | otro delito |
| El comentario {# … #} | “Solo me colé como texto. Inocente.” | coartada firme |
| Staleness & fingerprints | “Con 6 fingerprints, ¿cómo no ibas a dudar del binario?” | pista falsa |
| SITE_PUBLIC_PATH / symlink | “El dir no existía. Symlink para nada.” | pista falsa |
| generated.rs → vec!["content"] | “Soy el fallback. Ni me ejecuto.” | señuelo |
| El RBAC | “Un deny mío es un 302, encanto. Un 404 no es cosa mía.” | coartada 302 |
| build_page_generator | “Eso es de Leptos. En htmx-ssr ni aparezco.” | jurisdicción errónea |
El arma — El arma · un match hardcoded (preexistente, anti-PAP)
let kind = match base {
"blog" => Some("blog"),
"activities" | "actividades" => Some("activities"),
// … todos los kinds a mano … menos resources …
_ => None, // ← /recursos caía aquí → 404
};
Ruta registrada, content-type habilitado, la API devolvía items, el contenido presente — y aun así 404, porque un stub decidía la verdad en contra de la fuente de la verdad.
El giro — PAP-compliant · añadir un kind vuelve a ser solo NCL
let kind = load_routes_config().routes.iter().find_map(|r| {
if !r.enabled { return None; }
let seg = r.path.trim_start_matches('/').split('/').next().unwrap_or("");
(seg == base).then(|| r.content_type.clone()).flatten()
});
Todos los kinds, alias bilingües incluidos, resueltos desde la única fuente. Añadir un kind vuelve a ser solo NCL.
El veredicto
No fuimos a la playa por decidir mal: fuimos porque el mapa tenía una deriva sin señalizar. Las cuatro cosas que ontoref instrumenta son las cuatro que faltaban:
| Stub hardcoded que sombrea el registry | PAP + anti-patterns → grep-able |
| Conocimiento perdido, redescubierto por dolor | qa/howto → content-kind-howto |
| Construir sobre lo no-válido sin saberlo | review contra invariantes |
| Asumir lo no-asumible, creer lo no-creíble | estado declarado → 3 niveles |
La jurisprudencia — qué exige hoy la lección
- ✓El anti-pattern que este caso prometía —la lista a mano que sombrea el registry— por fin existe como restricción tipada, y se la vio FALLAR contra el árbol antes de arreglar nada
adr:002/hardcoded-kind-list-shadows-registry - ✓Y las ocho líneas volvieron a casa: las dos plantillas de rustelo resuelven el kind del registry, no de una lista a mano
contract:templates/*/crates/server/src/shell/htmx.rs#render_content_or_grid - ✓Que los match arms son una optimización y no la fuente de verdad — consultable en vez de redescubrible a golpes
qa:rustelo-static-page-howto
⊘Deuda declarada: Queda una cosa, y es la que hace que las de arriba muerdan o no: nada ejecuta la restricción. El justfile de rustelo no llama a ningún validador de ADRs — 23 restricciones en 8 ADRs, cero ejecutadas por receta o CI — así que la regla que ahora prohíbe este defecto está tan escrita, y tan callada, como lo estaba ADR-002 durante meses. La saldaría una receta just en la cadena de rustelo que las corra y reporte por restricción. Y con ella se arrastran dos que tampoco tienen dueño: la cura de la instancia sigue sin commitear (git status → M), y templates/ no está versionado — un git clean se lleva el backport y nadie se entera. El expediente 8/0 cuenta esto entero.
Una deriva de diez líneas, sin señalizar, nos costó una tarde salir — y se arregló en ocho. Ese diferencial es el ROI. Ontoref es la señalización del mapa.
Glosario
Sin coincidencias.