Para qué sirve esta página
Documenta cómo está construida y cómo se opera la tool Dependencias (/tools/deps): el chequeo de frescura de dependencias de CreaRack-Pro y CreaRackSL-workspace. Versión para usuario (qué hace, cómo leerla): [[feature—workspace—tool-dependencias]].
La tool no sustituye a Dependabot ni a pip-audit. Es un mirador unificado de frescura + seguridad (¿hay versión nueva, de qué tamaño es el salto, y tiene la versión en uso alguna vulnerabilidad conocida?) de los dos repos a la vez, en el sitio donde el equipo ya mira. Desde el 11-07-2026 el eje de CVEs lo cubre la propia tool vía OSV.dev (antes solo pip-audit sobre el lockfile del Local Agent).
Arquitectura (3 piezas)
GitHub Actions (CreaRack-Pro) Cloudflare (Workspace)
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ deps-freshness.yml (cron L) │ POST │ /api/tools/deps (Pages Fn) │
│ └─ scripts/deps_freshness.py│ ──────► │ └─ D1: deps_freshness │
│ + deps_specs.py │ Bearer │ (DELETE + INSERT batch) │
│ + deps_sources.py │ └──────────────┬───────────────┘
│ + deps_holds.py │ │ GET
│ • parsea manifiestos │ ┌──────────────▼───────────────┐
│ • resuelve horquillas │ │ /tools/deps (Astro + React) │
│ • aplica retenciones │ │ DepsPanel.tsx → tabla │
│ • "instalada": PROD → │ └───────────────────────────────┘
│ runner → lockfile │
│ • consulta PyPI / npm │
│ • calcula desfase semver │
│ • cruza con OSV.dev (CVE)│
└─────────────────────────────┘
│ GET + Bearer (INTERNAL_TOOLS_TOKEN)
▼
https://crearack.com/api/installed-packages (Django · PROD)
Es el mismo patrón que ai-eval / dashboard: colector en CI → endpoint Pages Function → D1 → React island en Astro.
Nota (28-08-2026): los campos
hold_*(ver «Retenciones deliberadas» abajo) ya viajan en elPOST /api/tools/deps, tienen columna D1 dedicada (migración0053) y se pintan en la UI — badge ámbar, tarjeta y pestaña propias. El desplegable «¿Cómo leo esta tabla?» vive ahora enDepsExplainer.tsx, separado deDepsPanel.tsx(Regla 5: el panel queda en 464 líneas).
Archivos
| Repo | Archivo | Rol |
|---|---|---|
| CreaRack-Pro | scripts/deps_freshness.py | Colector · CLI (cross-platform, stdlib pura) |
| CreaRack-Pro | scripts/deps_specs.py | Resolución de horquillas: PEP 440 (PyPI) y caret, tilde, rangos y alternativas (npm) → mayor versión publicada que el spec admite; además has_upper_bound y major_key |
| CreaRack-Pro | scripts/deps_sources.py | De dónde salen los datos: dataclass Dep, parsers de requirements.txt / package.json y lectores de “instalada” (pip list, package-lock.json, pnpm list) |
| CreaRack-Pro | scripts/deps_holds.py | Retenciones deliberadas: carga y valida deps_holds.json, marca las filas retenidas a propósito |
| CreaRack-Pro | deps_holds.json | Catálogo de retenciones vigentes (raíz del repo) — no genera build, se lee en runtime del colector |
| CreaRack-Pro | tests/scripts/test_deps_freshness.py | Casos sin red: resolución de horquillas, sin-techo, fix_within_spec, dedup, sonda a PROD, retenciones |
| CreaRack-Pro | config/urls.py → GET /api/installed-packages | Endpoint interno (Bearer INTERNAL_TOOLS_TOKEN) que devuelve las versiones Python REALES del contenedor de PROD |
| CreaRack-Pro | .github/workflows/deps-freshness.yml | Cron semanal + dispatch + push a manifiestos, a deps_holds.json o a alguno de los 4 scripts/deps_*.py |
| Workspace | migrations/0042_create_deps_freshness.sql | Tabla D1 deps_freshness |
| Workspace | migrations/0048_deps_freshness_vulns.sql | Columnas del eje de seguridad (vuln_count, vuln_ids, vuln_severity) |
| Workspace | migrations/0052_deps_freshness_motor_fiable.sql | Seis columnas del motor fiable (manifests, resolved, installed_source, unbounded, unbounded_major, fix_within_spec) |
| Workspace | migrations/0053_deps_freshness_holds.sql | Cuatro columnas de retenciones (hold_reason, hold_since, hold_ref, hold_until) |
| Workspace | functions/api/tools/deps/index.ts | API: GET (lee) / POST (reemplaza snapshot) |
| Workspace | src/pages/tools/deps/index.astro | Página |
| Workspace | src/components/tools/DepsPanel.tsx | Tabla + filtros + tarjetas de resumen |
| Workspace | src/components/tools/DepsBadges.tsx | Piezas de fila: LagCell (atraso o «Retenida a propósito»), InstalledCell (versión en uso + de dónde sale), UnboundedBadge (sin techo), FixScopeBadge (basta redeploy / cambiar pin), ManifestsNote (en N manifiestos) |
| Workspace | src/components/tools/DepsExplainer.tsx | Desplegable «¿Cómo leo esta tabla?», extraído de DepsPanel.tsx (Regla 5) |
| Workspace | src/components/tools/DepInfoModal.tsx | Ficha de paquete (modal): registro en vivo + catálogo + bloque «Por qué no se actualiza» si está retenida |
| Workspace | src/components/tools/depsShared.ts | Tipos y etiquetas compartidos (panel ↔ modal), incluidas isHeld/holdTitle |
| Workspace | src/components/tools/depsCatalog.ts | Catálogo curado: descripción en español de cada paquete |
| Workspace | src/components/tools/ToolsHub.tsx | Card en el hub /tools |
| Workspace | src/styles/tools.css | Estilos .deps-* |
Retenciones deliberadas (deps_holds.json, desde v1.86.10)
Una dependencia parada A PROPÓSITO (redis en 7.4.1 porque la serie 8.x corta el channel layer en vivo, Django <6.1 porque django-prometheus lo exige, ruff/mypy con pin exacto hasta auditar sus reglas nuevas) no es lo mismo que una dependencia olvidada — pero hasta este cambio salía en rojo igual que cualquier atrasada.
deps_holds.json (raíz de CreaRack-Pro) declara el catálogo. Cada entrada: package, ecosystem (pypi|npm), repo (crearack-pro|workspace), reason (llano), since (YYYY-MM-DD), ref (task/issue/PR) y opcionalmente until (condición o fecha para revisarla). scripts/deps_holds.py la carga (load), la valida (campos obligatorios, ecosystem/repo en catálogo, since con formato de fecha, sin duplicados) y la cruza contra las filas del colector (apply), rellenando hold_reason/hold_since/hold_ref/hold_until en el Dep que casa por (repo, ecosystem, paquete normalizado).
- Fichero ausente → 0 retenciones, sin ruido (falla suave).
- Fichero mal escrito →
HoldsError, que el colector convierte en::error::yexit 2— a propósito duro: un hold que no se aplicase pasaría desapercibido. - Hold sin dependencia que case (paquete borrado o mal escrito en
deps_holds.json) →::warning::en el log, no rompe el run. - En el resumen: una fila retenida con desfase
major/minorno suma en esos contadores del resumen (lag_counts); se cuenta aparte como «retenidas a propósito».
Retenciones vigentes (28-08-2026), todas task #275: redis 7.4.1 (task #204 · redis-py #2807 abierta), Django <6.1 (django-prometheus 2.5.0), ruff y mypy (pin exacto hasta septiembre 2026, con Dani y Txell).
En la UI (desde 28-08-2026): una fila retenida ya no se pinta en rojo/naranja — el distintivo de atraso pasa a ámbar apagado con el texto «Retenida a propósito» (DepsBadges.tsx → LagCell), y el tooltip trae el motivo más el «desde», la referencia y el «revisar» (holdTitle() en depsShared.ts). Los contadores «Muy atrasadas»/«Atrasadas», la frase-resumen de arriba y la cifra de la tarjeta de Dependencias en /tools excluyen las retenidas: tienen su propia tarjeta «Retenidas a propósito» (toggle-filtro) y su propia pestaña Retenidas junto a «Pendientes»/«Todas». La ficha del paquete (DepInfoModal.tsx) repite el mismo detalle, más ordenado, en el bloque «Por qué no se actualiza».
De dónde sale cada dato
-
Pineada (columna «Pedida» en la UI): el spec crudo del manifiesto (
==6.0.4,^9.3.0,>=2.10.0,<3…). La UI muestra la versión limpia y deja el spec en el tooltip. -
Última: API JSON oficial — PyPI
https://pypi.org/pypi/<pkg>/json(info.version) y npmhttps://registry.npmjs.org/<pkg>(dist-tags.latest). Fuente real, nunca memoria del modelo. -
Resuelta (campo
resolved, desde 28-08-2026): la mayor versión publicada que la horquilla admite — la que de verdad se instalaría hoy en un despliegue limpio. La calculadeps_specs.resolve()contra la lista completa de versiones del registro, con PEP 440 (packagingsi está disponible, respaldo propio si no) para PyPI y con caret/tilde/rangos/||para npm. Descarta prereleases salvo que no quede ninguna final que encaje. Specs opacos (workspace:,file:,git+…) no se resuelven.Por qué existe: antes se evaluaba la horquilla por su mínimo (
requests>=2.31.0→ «2.31.0»). Eso inventaba avisos de seguridad que no existían en ninguna instalación real — 30 falsos positivos el 28-08-2026 (22 deaiohttp, 6 derequests, 2 depytest). -
Instalada (columna «En uso»), en orden de preferencia y con la fuente guardada en
installed_source:Ámbito Manifiesto Fuente de “instalada” installed_sourceBackend Python requirements.txtPROD real: GET https://crearack.com/api/installed-packages(BearerINTERNAL_TOOLS_TOKEN) →importlib.metadata.distributions()del contenedorprodBackend Python (respaldo) requirements.txtpip install -r+pip list --format=jsondel runner de CIrunnerLocal Agent terminal/agent/requirements-agent.lock= pineada (lockfile 100% ==)lockfileTests tests/requirements.txt·tests/parity_validation/requirements.txtsin instalada (no se instalan en este job; is_dev=1) → se usa la resuelta— Frontend frontend/package.jsonfrontend/package-lock.json(leído del repo)lockfileWorkspace package.jsonpnpm install+pnpm list --depth 0 --json(en CI)runnerEl colector no ejecuta
pip/pnpm: el workflow genera los JSON y se los pasa por flag (--backend-installed,--workspace-installed), y la sonda a PROD va por--prod-versions-url+--prod-token. Falla suave: si PROD no contesta o devuelve vacío, se imprime un::warning::y la columna cae al runner; el run no se rompe. -
Desfase (columna «Atraso»): comparación semver de
(instalada || resuelta)vsúltima→major/minor/patch/uptodate/unknown. Tolera calver (2026.2.20) y sufijos (-preview,rc). Desde v1.86.10, una fila retenida a propósito (ver arriba) sigue calculando su desfase real, pero no suma en los contadoresmajor/minordel resumen. -
Sin techo (
unbounded/unbounded_major, desde 28-08-2026):unbounded= la horquilla no pone límite superior (>=Xen PyPI,>=X/*en npm;<,<=,==,~=,^sí lo ponen).unbounded_major= además la versión que corre ya está en un major distinto al declarado en el manifiesto — bajo0.xel minor cuenta como major (los SDK de IA rompen igual de un0.52a un0.122). No es una alarma de seguridad: es aviso de descontrol, porque cada despliegue puede traerse un salto grande que nadie decidió. -
En N manifiestos (
manifests): tras deduplicar por(repo, ecosistema, paquete), la fila conserva todos los manifiestos donde aparece el paquete, separados por coma. Gana la horquilla más restrictiva (la de menor versión resuelta; a igualdad, la que pone techo) y esa queda enmanifest; la “instalada” se hereda de cualquiera de las filas del grupo que la tenga.is_devsolo se mantiene si todas lo eran. -
Seguridad (desde 11-07-2026):
POST https://api.osv.dev/v1/querybatch(lotes de 100, ecosistemasPyPI/npm) con la versión(instalada || resuelta)— nunca el mínimo del spec; sin versión resoluble no se consulta (OSV devolvería avisos de cualquier versión). El detalle de cada aviso sale deGET /v1/vulns/{id}:database_specific.severityda la severidad (GHSA: CRITICAL/HIGH/MODERATE/LOW; los PYSEC suelen venir sin nota →unknown) y los eventosfixeddeaffected[].rangesdanfix_within_spec. Falla suave: si OSV no responde,vuln_count=0. Campos:vuln_count,vuln_ids(coma-separados),vuln_severity(la peor). Sin API key. -
fix_within_spec(desde 28-08-2026), solo en filas con aviso:true= alguna versión con fix cabe dentro de la horquilla ya declarada → basta un redeploy (la UI dice «basta redeploy»).false= el fix queda fuera → hay que cambiar el pin en el manifiesto.null= OSV no publica versión con fix para ese aviso. Se exige que todos los avisos de la fila tengan fix dentro del spec para dartrue. -
hold_reason/hold_since/hold_ref/hold_until: no nulo = fila retenida a propósito. Columnas D1 dedicadas desde la migración0053(28-08-2026); antes viajaban en el JSON delPOSTsin columna propia. Ver «Retenciones deliberadas» arriba.
La ficha de paquete (modal, desde 11-07-2026)
Al pinchar el nombre de un paquete, DepInfoModal.tsx abre una ficha con dos fuentes:
- Catálogo curado (
depsCatalog.ts): descripción en español, en llano, de cada paquete de los manifiestos vigilados (~100 entradas + fallbacks por prefijo para familias@codemirror/*,@fontsource/*,@radix-ui/*,@types/*). Mantenimiento: al añadir una dependencia nueva a un manifiesto, añadir su línea aquí; si falta, el modal cae a la descripción oficial del registro (inglés) — no rompe nada. - Registro oficial en vivo (fetch desde el navegador del staff, con CORS abierto en ambos): PyPI
https://pypi.org/pypi/<pkg>/<version>/json(con la versiónlatestdel snapshot el JSON es mucho más ligero; dalicense_expression/classifiers,project_urls, fecha de publicación) y npmhttps://registry.npmjs.org/<pkg-urlencoded>/latest(description, license, homepage, repository). Decisión de diseño: no se persiste nada en D1 ni pasa por el colector — datos siempre frescos, cero migraciones; si el registro no responde, el modal lo dice y muestra solo los datos locales (falla suave). Cache en memoria por sesión de navegador.
Si el paquete está retenido, la ficha añade el bloque «Por qué no se actualiza» con hold_reason/hold_since/hold_ref/hold_until.
Las tarjetas del resumen son además filtros toggle de la lista (estado cardFilter, que tiene prioridad sobre el seg «Pendientes/Todas/Retenidas»); la tarjeta «Total vigiladas» limpia filtros.
Operación
Lanzar a mano (forzar comprobación)
gh workflow run deps-freshness.yml -R CreaRackSL/CreaRack-Pro
Añadir o quitar una retención
Editar deps_holds.json (raíz de CreaRack-Pro) y añadir/borrar la entrada correspondiente — no requiere tocar código ni migraciones. Borrar la entrada en cuanto se suba la dependencia (el propio fichero lo recuerda en su campo _doc).
Probar en local (sin publicar)
python scripts/deps_freshness.py --dry-run
Imprime la tabla por consola (con las columnas RESUELTA, la fuente de la instalada y las notas «sin techo» / «en N manifiestos» / «retenida»). Frontend y Local Agent traen “instalada”; backend y workspace saldrán ”—” salvo que pases --backend-installed pip-list.json / --workspace-installed pnpm-list.json. Para ver la instalada REAL de producción:
python scripts/deps_freshness.py --dry-run `
--prod-versions-url https://crearack.com/api/installed-packages `
--prod-token $env:INTERNAL_TOOLS_TOKEN
Secrets (repo CreaRack-Pro → Settings → Secrets → Actions)
| Secret | Uso | ¿Obligatorio? |
|---|---|---|
MCP_TOKEN | Bearer del Workspace (mismo que push-drift.yml) | Sí |
CF_ACCESS_CLIENT_ID / ..._SECRET | Service token CF Access | Opcional |
WORKSPACE_REPO_TOKEN | PAT read-only a CreaRackSL-workspace | Opcional* |
INTERNAL_TOOLS_TOKEN | Bearer de GET https://crearack.com/api/installed-packages — mismo valor que el env var de Dokploy en PROD | Opcional** |
*Sin WORKSPACE_REPO_TOKEN el run cubre solo CreaRack-Pro (el checkout del Workspace se salta sin romper el job). Para cubrir también el Workspace hay que añadir el PAT. Ver [[runbook—platform-credentials-map]].
**Sin INTERNAL_TOOLS_TOKEN la columna «En uso» del backend sale de lo que resuelve el runner del CI (installed_source = runner), no de lo que corre en producción. El workflow tiene un paso previo Probe PROD installed packages (continue-on-error) que hace el GET y deja el código HTTP en el log: 200 = la instalada vendrá de PROD. El endpoint de Django exige un token de ≥32 caracteres; por debajo de eso deniega todo, aunque el valor coincida.
Estado 28-08-2026: el secret está pendiente de crear en el repo CreaRack-Pro. Hasta entonces las pasadas usan el respaldo del runner. Dueño: Edu.
Migración D1
wrangler d1 execute crearacksl-workspace-db --remote --file=./migrations/0042_create_deps_freshness.sql
wrangler d1 execute crearacksl-workspace-db --remote --file=./migrations/0048_deps_freshness_vulns.sql
wrangler d1 execute crearacksl-workspace-db --remote --file=./migrations/0052_deps_freshness_motor_fiable.sql
wrangler d1 execute crearacksl-workspace-db --remote --file=./migrations/0053_deps_freshness_holds.sql
(El POST /api/tools/deps falla hasta que la tabla existe; el GET devolverá vacío y la tool mostrará “Sin datos todavía”.)
La 0052 (aplicada el 28-08-2026) añade las seis columnas del motor fiable. Es un ALTER TABLE ADD COLUMN por campo, y va antes de que corra el colector nuevo: sin ellas el INSERT del POST revienta. installed_source no lleva CHECK a propósito — D1 no lo valida de forma fiable en un ADD COLUMN y el enum ya se filtra en el handler.
La 0053 (aplicada el 28-08-2026) añade las cuatro columnas de retenciones — hold_reason, hold_since, hold_ref, hold_until, todas TEXT nulables. Mismo patrón: ALTER TABLE ADD COLUMN no idempotente, una sola pasada, antes de que el colector nuevo publique. Con esto, los campos hold_* dejan de depender solo del JSON del POST y tienen columna propia en deps_freshness.
Contrato del endpoint
GET /api/tools/deps→{ checked_at, count, items: [...] }, ordenado por: con vulnerabilidad primero (peor severidad arriba), luego desfase (major→uptodate). Auth: CF Access (navegador) vía_middleware.ts.POST /api/tools/deps→ body{ items: [...] }. HaceDELETE+INSERTbatch (snapshot completo, no histórico). Auth: Bearer (MCP_TOKENS) vía_middleware.ts. Devuelve{ inserted, checked_at }. Tope de 5000 filas por snapshot (el real ronda unos cientos). Cada fila lleva, además de los campos originales, los seis del motor fiable:manifests,resolved,installed_source(prod/runner/lockfile; cualquier otro valor →NULL),unbounded,unbounded_majoryfix_within_spec. Este último es tri-estado: solo un booleano explícito se guarda como 0/1; ausente o basura →NULL(«OSV no publica fix»), que es distinto de 0 («cambiar pin»). Desde la migración0053(28-08-2026) cada fila lleva tambiénhold_reason/hold_since/hold_ref/hold_until(no nulo = retenida;hold_sincese descarta si no esYYYY-MM-DD) en columnas D1 propias — un payload viejo sin ellos entra con los cuatro aNULL.
Troubleshooting
- La tabla sale vacía / “Sin datos”: ¿se aplicaron las migraciones 0042, 0048, 0052 y 0053? ¿ha corrido el workflow al menos una vez? Mira el último run en Actions.
- Ninguna fila dice
PRODen «En uso»: falta el secretINTERNAL_TOOLS_TOKENen CreaRack-Pro, o el valor no coincide con el env var de Dokploy en PROD, o tiene menos de 32 caracteres (el endpoint deniega por debajo de ese largo). Diagnóstico rápido: el paso «Probe PROD installed packages» del run imprime el código HTTP;401= token malo,200= bien. En la salida del colector, la línea de resumen dice'instalada' desde PROD=N. - Un paquete sale «sin techo» y no debería: el spec del manifiesto no pone límite superior. Se arregla en el manifiesto (añadir
<X), no en la tool. Los^/~de npm y los~=/==/<de PyPI sí cuentan como techo. - Un paquete aparece una sola vez estando en varios manifiestos: es el dedup por
(repo, ecosistema, paquete). La fila muestra la horquilla más restrictiva y lista el resto en «en N manifiestos» (tooltip). - Falta el Workspace en la tabla: falta
WORKSPACE_REPO_TOKEN, o elpnpm installfalló (pasocontinue-on-error, revisa el log). - Un paquete sale “unknown”: el registro no devolvió versión (404, nombre raro, o red caída en ese momento). Reintenta en el siguiente run.
majorfalso en calver: las versiones tipo2026.xse comparan numéricamente; un salto de año cuenta como major. Es esperado.- HTTP 200 pero no publica: el colector valida el body (
inserted) y devuelve exit ≠ 0 si la respuesta no cuadra (Regla 15) → el job sale en rojo. - El modal no muestra la descripción curada: verificar que el nombre exacto (lowercase) está en
DESCRIPTIONSdedepsCatalog.ts, o que la familia tiene fallback enPREFIX_DESCRIPTIONS(prefijo con la barra:@radix-ui/). Si falta, el modal cae a la descripción oficial del registro. - El job sale en rojo con
::error::sobredeps_holds.json(desde v1.86.10): el fichero está mal formado — falta un campo obligatorio,ecosystem/repofuera de catálogo,sinceno esYYYY-MM-DD, o una retención duplicada para el mismo(repo, ecosystem, package). El mensaje deHoldsErrordice exactamente cuál. ::warning::retención sin dependenciaen el log: el hold dedeps_holds.jsonno casa con ninguna fila del colector — el paquete se borró del manifiesto o el nombre está mal escrito en el fichero.- El
POSTempieza a fallar con error de columna (desde la migración 0053): revisar quehold_reason/hold_since/hold_ref/hold_untilexisten endeps_freshness— la migración va antes del primerPOSTque los incluya.
Extensiones previstas
Segundo eje CVE: cruzar con OSV para marcar lo que además tiene vulnerabilidad conocida.HECHO 11-07-2026 (columna Seguridad, OSV.dev).Ficha por paquete (qué es, licencia, repo).HECHO 11-07-2026 (modal + catálogo curado).Frontend de retenciones: pintarHECHO 28-08-2026 (badge ámbar víahold_reason/hold_refenDepsPanel.tsx/DepsBadges.tsxcomo badge «Retenida a propósito» en vez de rojo/naranja.LagCell, tarjeta y pestaña «Retenidas», bloque «Por qué no se actualiza» en la ficha del paquete).- Botón “ver cambios” por fila → Context7 para la guía de migración de la versión nueva (aquí Context7 sí aplica: trae docs, no números de versión).
- Señal a Pulse/alertas si algo está N majors atrás o con CVE.
Véase también
- [[feature—workspace—tool-dependencias]]
- [[entity—functions—endpoint—tools-deps]]
- [[entity—migrations—table—deps-freshness]]
- [[runbook—platform-credentials-map]]
- [[entity—ci—service—deps-freshness-collector]]