Volver a la wiki

Servicio: Colector de frescura de dependencias (Python)

Descripción general

Desde task #275 (28-08-2026, v1.86.4) el colector está troceado en 4 módulos para no pasar el límite de 500 LOC/módulo (Regla 5). El CLI y el punto de entrada del workflow siguen siendo scripts/deps_freshness.py; delega en:

Colecta el estado de frescura de dependencias en dos repositorios (CreaRack-Pro y CreaRackSL-Workspace) sin hardcodear paquetes.

El bug que motivó el troceo

Hasta v1.86.3, una horquilla como requests>=2.31.0 se evaluaba literalmente por el mínimo (2.31.0) en vez de por la versión que pip/npm instalarían de verdad (la mayor publicada que la satisface). Cruzar ese mínimo contra OSV.dev inventaba avisos de seguridad de versiones que nadie tenía instaladas: 22 HIGH de aiohttp, 6 de requests, 2 de pytest, contrastado contra el pip freeze real de PROD. deps_specs.py resuelve ahora la horquilla a la versión real (Dep.resolved) antes de consultar OSV — los tres casos bajaron a 0 avisos.

Retenciones deliberadas (deps_holds.json, v1.86.10)

Una dependencia parada A PROPÓSITO (redis 7.4.1 porque la serie 8.x corta el channel layer en vivo, Django 6.0 porque django-prometheus exige <6.1, ruff/mypy con pin exacto hasta auditar las reglas nuevas) no es una dependencia olvidada — pero antes de este cambio salía en rojo igual que una atrasada sin motivo. deps_holds.json (raíz de CreaRack-Pro) declara esas retenciones; deps_holds.py las carga y las cruza contra las filas del colector.

Cómo se resuelve “instalada” (por prioridad)

  1. PROD real — --prod-versions-url / --prod-token contra GET /api/installed-packages ([[entity—api—endpoint—installed-packages]]), el mismo contenedor que sirve producción. installed_source = "prod".
  2. Runner de CI — pip-list.json / pnpm-list.json generados en el propio job. installed_source = "runner".
  3. Lockfile — package-lock.json (100% ==). installed_source = "lockfile".

Si PROD no responde (fetch_prod_versions falla suave), cae al runner sin romper la ejecución.

Campos nuevos en Dep

CampoQué dice
manifestsTodos los manifiestos donde aparece el paquete tras deduplicar, separados por coma
resolvedMayor versión publicada que satisface pinned
installed_sourceprod | runner | lockfile | None
unboundedEl spec no pone techo superior (ej. openai>=2.26.0 sin <3)
unbounded_majorAdemás, el major efectivo ya se ha movido del declarado (bajo 0.x el minor cuenta como major — así se detectó que anthropic>=0.52.0 ya corría en 1.x)
fix_within_specTrue: el aviso OSV tiene fix dentro de la horquilla, basta un redeploy · False: hay que cambiar el pin a mano · None: sin datos de fix
hold_reason / hold_since / hold_ref / hold_until(v1.86.10) No nulo = fila retenida a propósito. Motivo, fecha de la decisión, task/issue/PR que la respalda, y condición o fecha para revisarla — ver [[#retenciones-deliberadas-deps_holds-json-v1-86-10]] arriba

Deduplicación

dedupe(deps) colapsa a una fila por (repo, ecosistema, paquete): antes, un paquete presente en varios manifiestos salía repetido (asyncssh ×2, requests ×3, pysnmp ×2). Se queda con la horquilla más restrictiva (restrictiveness()) y lista el resto en manifests. El snapshot pasó de ~130 filas a 109 en el primer dry-run tras el cambio.

Manifiestos soportados

RepoEcosistemaRutaInstalada desde
CreaRack-ProPyPIrequirements.txtPROD → pip list --format=json (CI) → lockfile
CreaRack-ProPyPIterminal/agent/requirements-agent.lockLockfile (100% ==)
CreaRack-ProPyPItests/requirements.txt, tests/parity_validation/requirements.txtSolo pineadas (is_dev=True)
CreaRack-Pronpmfrontend/package.jsonfrontend/package-lock.json
Workspacenpmpackage.jsonpnpm list --json (CI)

Funciones principales (por módulo)

deps_sources.py: parse_requirements, parse_package_json (parsers de manifiesto), normalize_pypi (PEP 503), load_pip_list, load_package_lock, load_pnpm_list (lectores de “instalada”).

deps_specs.py: version_key/is_prerelease/major_key (comparación de versiones), pypi_satisfies/npm_satisfies (¿esta versión cumple el spec?), has_upper_bound, resolve (mayor versión publicada que satisface un spec).

deps_holds.py: load (lee y valida deps_holds.json), apply (marca las filas retenidas y avisa de los holds huérfanos).

deps_freshness.py: resolve_registry (consulta PyPI/npm y trae la lista de versiones publicadas), resolve_specs (calcula resolved/unbounded/unbounded_major por dep), resolve_vulns (cruza resolved contra OSV.dev, calcula fix_within_spec), fetch_prod_versions (GET al endpoint de PROD), dedupe, lag_counts (resumen de desfase, resta las retenidas de major/minor), collect (orquestador), publish (POST a /api/tools/deps, valida el body — Regla 15).

Ejecución en local

python scripts/deps_freshness.py --dry-run

Resuelve versiones sin necesidad de CI y sin token. Output: tabla en stdout, con la cuenta de «retenidas a propósito» aparte.

Ejecución en CI

Activada por [[entity—ci—workflow—deps-freshness]] (cron semanal lunes 06:30 UTC, push a manifiestos, a deps_holds.json o a alguno de los 4 módulos, o manual).

Tolerancias / Comportamiento robusto

Reglas aplicadas

Véase también

Subir