Incidente: 41 nodos TS huérfanos por modo additive-only en bib_ast_ts (s50-fix-4)
Resumen
Durante aproximadamente 8 días (desde el 27-28 de abril de 2026 hasta el 6 de mayo de 2026), el extractor TypeScript AST (scripts/bib_ast_ts.mjs) operó en modo additive-only: nunca purgaba nodos obsoletos del grafo de conocimiento aunque los archivos fuente correspondientes hubiesen sido eliminados o renombrados. Resultado: 41 nodos TS huérfanos (33 functions + 8 js_module) permanecieron indexados con last_indexed_at del 27-28 de abril para siempre.
Causa raíz
pushToMCP recibía replace_apps: true en el payload pero ignoraba silenciosamente el flag — nunca enviaba el campo replace_app al handler bib_index_ast. El handler, al no recibir replace_app, asumía modo additive y no ejecutaba la purga de qualified names ausentes del payload.
Extractor pushToMCP bib_index_ast handler
───────────── ───────── ─────────────────────
replace_apps=true ──────► flag ignorado ──────► sin replace_app ──► additive-only
POST único con NO purga stales
todos los nodos
Nodos afectados (muestra conocida)
| Tipo | Origen | Motivo de obsolescencia |
|---|---|---|
js_module × 8 | src/components/ (varios) | Componentes V1 eliminados / reestructurados |
function × 33+ | V1HomeVivo, SearchModal (viejo), translateWithGemini (renombrado) | Componentes eliminados o renombrados |
Fix aplicado — commit 6bdc1c4
Se refactorizó pushToMCP en dos funciones:
-
postChunkToMCP(mcpUrl, token, nodes, edges, replaceApp)— función atómica que construye el JSON-RPC y envía un POST. Incluyereplace_appenargumentscuando se le pasa. -
pushToMCP(renovada) — cuandoreplace_apps=true, itera las apps únicas presentes en el payload y llama apostChunkToMCPpor cada una conreplace_app: <name>. El handler purga del subgrafo de esa app losqualified_nameque no vengan en el payload.
// Antes (modo additive-only involuntario)
params: { name: 'bib_index_ast', arguments: { ast_json: { nodes, edges } } }
// Sin replace_app → handler no purga
// Después (idempotente por app)
for (const app of apps) {
await postChunkToMCP(mcpUrl, token, appNodes, appEdges, app);
// └── replace_app enviado
}
Bonus: detección defensiva de errores
El fix también unifica la detección de errores del handler, que puede responder con error singular (wrapper Unhandled) o errors[] plural (handlers internos) — mismo patrón footgun que se corrigió el mismo día en bib_openapi.py bajo Regla 15:
if (inner?.error || (Array.isArray(inner?.errors) && inner.errors.length > 0)) {
throw new Error(`MCP handler returned: ${JSON.stringify(inner.error || inner.errors)}`);
}
Impacto
- Calidad del grafo de conocimiento: degradada durante ~8 días en el espacio TypeScript del grafo. Agentes y búsquedas que dependiesen de nodos TS podían obtener resultados obsoletos.
- Severidad: media — no hay pérdida de datos de producción, solo contaminación del índice de conocimiento.
- Resolución: automática tras el siguiente run de
bib_ast_ts.mjsconreplace_apps=truepost-fix.
Regla derivada (Regla 15 — extensión)
Todo script que acepte un flag
replace_*DEBE verificar en su capa de envío que el flag se propaga al handler. El contrato entre extractor y handler debe testearse explícitamente con un payload vacío por app para confirmar purga.
Este patrón footgun ya estaba documentado para bib_openapi.py. El mismo día que se descubrió en TS se extendió la detección defensiva de error/errors[] a ambos scripts.
Cronología
| Fecha | Evento |
|---|---|
| 2026-04-27/28 | Primer indexado con modo additive-only; 41 nodos ya eliminados del código quedan en el grafo |
| 2026-05-06T08:42:39Z | @Esquembri detecta y corrige en commit 6bdc1c4 (s50-fix-4) |
| Post-fix | Siguiente run de bib_ast_ts.mjs purga los 41 nodos huérfanos |
Véase también
- [[feature—biblioteca—bib-reindex-extras]]
- [[entity—functions—handler—stale-check-cron]]