Endpoint /api/tools/deps — API REST de Dependencias
Propósito
Endpoint Pages Function (Cloudflare Workers) que expone el estado de frescura de dependencias del sistema: lee la tabla deps_freshness ([[entity—migrations—table—deps-freshness]]) y permite que el colector (scripts/deps_freshness.py en CreaRack-Pro) publique nuevos snapshots vía POST.
Ubicación
- Archivo:
functions/api/tools/deps/index.ts - Ruta:
/api/tools/deps - Entorno: Cloudflare Pages Functions
- Database: D1 (
DB)
Autenticación
- GET: CF Access vía
_middleware.ts(navegador del staff con acceso a Workspace). - POST: Bearer token (
MCP_TOKENS) vía_middleware.ts(el colector de GitHub Actions).
Métodos
GET /api/tools/deps
Propósito: Obtener el snapshot actual de frescura de dependencias, ordenado por avisos de seguridad primero y luego por mayor desfase.
Response (JSON):
{
"checked_at": "2026-08-28T13:00:00Z",
"count": 147,
"items": [
{
"repo": "crearack-pro",
"ecosystem": "pypi",
"manifest": "requirements.txt",
"package": "redis",
"pinned": "==7.4.1",
"installed": "7.4.1",
"latest": "8.2.0",
"lag_kind": "major",
"is_dev": 0,
"vuln_count": 0,
"vuln_ids": null,
"vuln_severity": null,
"manifests": "requirements.txt",
"resolved": "7.4.1",
"installed_source": "prod",
"unbounded": 0,
"unbounded_major": 0,
"fix_within_spec": null,
"hold_reason": "La serie 8.x corta el channel layer en vivo",
"hold_since": "2026-08-28",
"hold_ref": "task #204 · redis-py #2807",
"hold_until": "cuando redis-py #2807 se cierre",
"checked_at": "2026-08-28T13:00:00Z"
}
]
}
Campos de respuesta:
checked_at(ISO 8601): timestamp del snapshot (NULL si tabla vacía).count(int): número total de filas.items(array): ver [[entity—migrations—table—deps-freshness]] para el detalle de cada columna — elSELECTdevuelve la fila completa, incluidos los campos de seguridad (0048), del motor fiable (0052) y de retenciones (0053:hold_reason/hold_since/hold_ref/hold_until, no nulo = fila retenida a propósito).
Orden interno (ORDER BY, en este orden): 1) filas con vuln_count > 0 primero, 2) vuln_severity (critical → high → moderate/medium → low → resto), 3) lag_kind (major → minor → patch → unknown → resto), 4) repo, 5) lower(package) (alfabético, sin distinguir mayúsculas). El orden no distingue retenidas — eso lo filtra la UI (pestaña «Retenidas»).
POST /api/tools/deps
Propósito: Reemplazar completamente el snapshot (operación idempotente: DELETE + INSERT batch).
Request (JSON): mismo shape que un item del GET (ver arriba), dentro de {"items": [...]}.
Campos requeridos por fila: repo, ecosystem, manifest, package. Las filas sin alguno de los cuatro se ignoran silenciosamente (continue).
Campos opcionales (admiten null/ausente):
pinned,installed,latest.lag_kind: si falta o no está en['major','minor','patch','uptodate','unknown']→'unknown'.is_dev: boolean → 0/1.vuln_count(default 0 si no es un número finito),vuln_ids,vuln_severity(si no está en el set válido →null, no'unknown').- Motor fiable (task #275, payloads viejos entran con todo
null/0):manifests/resolved: string no vacía onull(el colector manda''cuando no hay valor; se normaliza anull).installed_source: si no está en['prod','runner','lockfile']→null.unbounded/unbounded_major: boolean → 0/1 (cualquier valor que no seatrueliteral → 0).fix_within_spec: tri-estado real — solo se guarda 0/1 si el valor entrante es un boolean explícito; cualquier otra cosa (ausente,null, basura) →NULLen BD. Distingue “OSV no publica versión con fix” (NULL) de “hay que cambiar el pin” (0).
- Retenciones deliberadas (migración
0053, task #275):hold_reason,hold_ref,hold_until— texto no vacío onull(text()).hold_since— solo se guarda si casa conYYYY-MM-DD(isoDay()); cualquier otro formato →null. Fila retenida =hold_reasonno nulo; los otros tres son contexto opcional. Un payload viejo sin estos cuatro campos entra con todos anull(fila no retenida).
Response (JSON): {"inserted": N, "checked_at": "..."}. 400 si body.items no es array, o si trae más de 5000 filas (cap de tamaño, WS6 B5: el snapshot real ronda unos cientos de paquetes, así que 5000 es margen amplio y evita un batch D1 desmesurado — DELETE + N INSERT atómicos — si llegara un body gigante).
Comportamiento: DELETE FROM deps_freshness → INSERT batch (env.DB.batch()) → responde inserts + timestamp.
Dependencias
- D1: tabla
deps_freshness— ver [[entity—migrations—table—deps-freshness]] (creada en 0042, ampliada en 0048, 0052 y 0053). - _middleware.ts: valida auth (CF Access o Bearer token).
Consumidores
- Navegador:
DepsPanel.tsx(React) enGET, badges de seguridad/fuente/techo/retención delegados aDepsBadges.tsx, y el desplegable de ayuda aDepsExplainer.tsx. - Página:
/tools/deps(src/pages/tools/deps/index.astro) montaDepsPanelcomo isla React. - Hub de tools:
ToolsHub.tsxllama también alGETy agrega los counts (total de filas y cuántas están desactualizadas segúnlag_kind, excluyendo las retenidas) para la tarjeta de/tools. - Colector:
scripts/deps_freshness.py(CreaRack-Pro) enPOSTpara publicar snapshot semanal. Desde v2.0 obtieneinstalled_source: "prod"consultando [[entity—api—endpoint—installed-packages]]; si no responde, cae a"runner". Desde v1.86.10,scripts/deps_holds.pyrellena los cuatro camposhold_*contradeps_holds.jsonantes de publicar.
Notas de diseño
- Snapshot, no histórico: el
POSTreemplaza completamente el estado. No hay histórico de cambios. - Batch atómico: todas las inserciones ocurren en una sola transacción.
- Tolerancia a datos parciales: un item sin versión final o sin los campos del motor fiable/retenciones no rompe el endpoint; entra con
NULL/0 y la UI lo trata como “sin dato” o “no retenida”. installed_sourceylag_kindse validan en el handler, no conCHECKen la migración (SQLite/D1 no lo aplica de forma fiable en unALTER TABLE ADD COLUMN).
Véase también
- [[entity—migrations—table—deps-freshness]]
- [[feature—workspace—tool-dependencias]]
- [[runbook—workspace-tech—tool-dependencias]]
- [[concept—saas—observability]]
- [[entity—api—endpoint—installed-packages]]