CreaRack-SL

Bibliotecario-Ingest Batch: commit consolidado por PR (s80)

Contexto y motivación

Antes de s80 (PR#63), cada PR mergeado disparaba 3-5 commits autónomos del Bibliotecario-Ingest al workspace — uno por cada página wiki creada o actualizada. Cada commit cancelaba el deploy CF Pages anterior, generando una cascada que alargaba el deploy final a 15-20 minutos (queja s79, @Esquembri).

Solución implementada

Se eligió la Opción A del análisis técnico: refactorizar el handler MCP y el script de ingesta para parquear los .md en una tabla D1 transitoria (bib_wiki_pending_commits) durante el agentic loop de Sonnet, y consolidar todos los archivos en un único commit al final vía GitHub Git Data Trees API multi-file.

Componentes del sistema

1. Tabla D1 bib_wiki_pending_commits (migration 0036)

Almacena temporalmente el cuerpo de cada página wiki pendiente de commit.

CREATE TABLE bib_wiki_pending_commits (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  trigger_ref TEXT NOT NULL,
  slug TEXT NOT NULL,
  file_path TEXT NOT NULL,
  body TEXT NOT NULL,
  operation TEXT NOT NULL CHECK (operation IN ('create', 'update')),
  commit_message TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now')),
  UNIQUE(trigger_ref, slug)
);

La restricción UNIQUE(trigger_ref, slug) con ON CONFLICT DO UPDATE garantiza idempotencia: si Sonnet hace create + update sobre el mismo slug en la misma sesión, gana el último cuerpo, y la operación preserva 'create' si era la original.

2. wiki_create_page y wiki_update_page — modo defer_commit

Ambas tools MCP aceptan ahora dos parámetros opcionales:

ParámetroTipoDescripción
defer_commitbooleanSi true, el .md se escribe en bib_wiki_pending_commits en vez de hacer commit inmediato.
trigger_refstringIdentificador del trigger batch (ej. PR#63). Obligatorio si defer_commit=true.

Comportamiento por defecto sin cambios: defer_commit es false; las tools siguen committeando inmediato (ghPutFile). wiki_archive_answer y skills manuales no se ven afectados.

Para wiki_update_page: el parking solo aplica si el patch incluye content (body real). Los updates metadata-only siguen siendo D1-only sin tocar el .md.

3. Nuevo MCP tool: wiki_flush_pending_commits

Consolida todas las páginas parqueadas para un trigger_ref dado en un solo commit a main.

Flujo interno (GitHub Git Data Trees API):

  1. GET /repos/{REPO}/git/refs/heads/{BRANCH} → obtiene headSha
  2. GET /repos/{REPO}/git/commits/{headSha} → obtiene baseTreeSha
  3. POST /repos/{REPO}/git/blobs × N (uno por archivo, base64)
  4. POST /repos/{REPO}/git/trees con base_tree=baseTreeSha + N entries
  5. POST /repos/{REPO}/git/commits → nuevo commit
  6. PATCH /repos/{REPO}/git/refs/heads/{BRANCH} → fast-forward main

Optimización N=1: si solo hay una página pendiente, usa ghPutFile clásico (3 subrequests menos que Trees API).

Idempotencia: el DELETE de filas pending solo ocurre tras commit OK. Si el flush falla, las filas persisten y se pueden re-flushear manualmente.

Parámetros:

ParámetroRequeridoDefaultDescripción
trigger_ref✅—Identificador del batch
actor❌agent-ingestQuién dispara el flush
message_prefix❌wiki(batch)Prefijo del commit message

Respuesta ejemplo:

{
  "ok": true,
  "flushed": 3,
  "commit_sha": "abc1234...",
  "trigger_ref": "PR#63",
  "creates": ["entity--racks--model--rack"],
  "updates": ["feature--monitoring--cns-v1"]
}

4. bib_ingest.py — inyección automática

El script CI inyecta defer_commit=true + trigger_ref en cada llamada a wiki_create_page / wiki_update_page durante el agentic loop:

if tool_name in ("wiki_create_page", "wiki_update_page"):
    tool_input = dict(tool_input)
    tool_input["defer_commit"] = True
    tool_input["trigger_ref"] = trigger_ref

Al final del loop (si artifacts no está vacío), llama a wiki_flush_pending_commits:

flush_raw = call_mcp(
    "wiki_flush_pending_commits",
    {"trigger_ref": trigger_ref, "actor": "agent-ingest"},
    mcp_token,
)

El resultado logea: [Ingest] Batch flush: N pages → commit abc1234…

Compatibilidad hacia atrás y hacia adelante

EscenarioComportamiento
bib_ingest.py antiguo + MCP nuevoMCP ignora defer_commit ausente → commit inmediato (legacy)
bib_ingest.py nuevo + MCP antiguoFlush devuelve “tool not found” como WARN no fatal; commits inmediatos siguen funcionando
Skills manuales / wiki_archive_answerSin cambios — siempre commit inmediato

Impacto en tiempos de deploy

Antes (s79)Después (s80)
3-5 commits por PR → cascada de cancels1 commit por PR al final del loop
Deploy CF Pages: 15-20 minDeploy CF Pages: ~3-5 min (un solo trigger)

Archivos del PR#63

  • migrations/0036_wiki_pending_commits.sql — nueva tabla D1
  • functions/api/mcp/handlers/wiki.ts — lógica defer + wikiFlushPendingCommits
  • functions/api/mcp/tools.ts — descriptores actualizados + tool nueva
  • functions/api/mcp/index.ts — LOGGED_TOOLS extendido
  • .github/scripts/bib_ingest.py — inyección defer + llamada flush

Véase también

  • [[entity—biblioteca—table—bib-wiki-pending-commits]] — Tabla D1 que almacena temporalmente los .md pendientes de commit batch
  • [[concept—general—existe-documentacion-sobre-wikicreatepage-wikiupda]] — Contexto de las tools MCP wiki_create_page / wiki_update_page que adquieren el modo defer

Page aislada parcialmente: dominio nuevo (batch ingest pipeline) sin precedentes directos en la wiki. Se añadirán más relacionados conforme se documenten entidades del sistema Biblioteca.