CreaRack-SL

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)

TipoOrigenMotivo de obsolescencia
js_module × 8src/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:

  1. postChunkToMCP(mcpUrl, token, nodes, edges, replaceApp) — función atómica que construye el JSON-RPC y envía un POST. Incluye replace_app en arguments cuando se le pasa.

  2. pushToMCP (renovada) — cuando replace_apps=true, itera las apps únicas presentes en el payload y llama a postChunkToMCP por cada una con replace_app: <name>. El handler purga del subgrafo de esa app los qualified_name que 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.mjs con replace_apps=true post-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

FechaEvento
2026-04-27/28Primer 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-fixSiguiente 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]]