Skip to content

English · Español

Solución 01 — referencia de just manifest

Léelo solo tras completar ../lab/01-write-the-justfile.md y commitear tu intento. Compara contra esta referencia, no antes.

Receta de referencia

Añade a /Justfile:

# Record a per-experiment manifest. Used by every numeric experiment from Phase 4+.
# Usage: just manifest <topic>
manifest topic:
    #!/usr/bin/env bash
    set -euo pipefail
    safe_topic=$(printf '%s' "{{topic}}" | tr -c '[:alnum:]._-' '-' )
    dir="experiments/$(date +%F)-${safe_topic}"
    mkdir -p "$dir"
    versions=$(uv run python -c "from utils.seeding import log_versions; import json; print(json.dumps(log_versions()))")
    git_sha=$(git rev-parse HEAD 2>/dev/null || echo "no-git")
    if git diff-index --quiet HEAD -- 2>/dev/null; then
      git_dirty=false
    else
      git_dirty=true
    fi
    cpu=$(awk -F: '/^model name/{print $2; exit}' /proc/cpuinfo | sed 's/^ //')
    kernel=$(uname -r)
    os=$(. /etc/os-release && echo "$PRETTY_NAME")
    cat > "$dir/manifest.json" <<JSON
    {
      "id": "$(date +%F)-${safe_topic}",
      "git_sha": "${git_sha}",
      "git_dirty": ${git_dirty},
      "seed": 42,
      "versions": ${versions},
      "hardware": { "cpu": "${cpu}", "kernel": "${kernel}", "os": "${os}" },
      "started_at": "$(date -u +%FT%TZ)"
    }
    JSON
    echo "$dir/manifest.json"

Decisiones tomadas en esta referencia

  • set -euo pipefail — falla ruidosamente ante cualquier error, variable indefinida o fallo de pipeline. Sin esto, un git ausente produciría silenciosamente git_sha: "".
  • safe_topic — reemplaza cualquier cosa no alfanumérica (más ., _, -) por -. Previene que topic=foo;rm -rf . inyecte comandos.
  • #!/usr/bin/env bash — la receta es un único script bash (just soporta shebangs). Esto es más limpio que encadenar comandos con &&.
  • /proc/cpuinfo — extracción amigable con Fedora del modelo de CPU. Degrada elegantemente si el archivo no está (produciría cpu vacío, pero set -e está activo así que fallaríamos antes de producir un medio-manifiesto — aceptable).
  • started_at solamente, sin finished_at — esta receta registra el manifiesto de inicio; el script del experimento que lo consume actualiza finished_at y wall_seconds cuando termina. Acoplarlos forzaría a cada experimento a invocar just manifest y un paso de fin, lo cual es incómodo.
  • Idempotencia: reejecutar la receta con el mismo topic el mismo día sobrescribe el manifiesto. Defendible: una reejecución es una reejecución. Si quieres historial, usa otro topic.

Qué marcaría en una review

  • Las versions se capturan fuera del proceso del experimento — así que si tu script de experimento cambia su estado de venv, el manifiesto queda obsoleto. El record_manifest real en src/utils/ se llamará dentro del experimento, que es correcto. Esta receta es una herramienta rápida ad-hoc.
  • Sin campos pid, user, host. Añádelos si colaboras o corres en hardware compartido.
  • La receta bloquea por la disponibilidad de git. Para un checkout sin git (raro), cae a "no-git" — aceptable para un currículo, no para un entorno regulado.

Test de referencia

Crea tests/test_justfile.py:

import json
import re
import subprocess
from pathlib import Path

def test_manifest_recipe(tmp_path: Path, monkeypatch) -> None:
    # The recipe writes inside the repo; we'd ideally redirect to tmp_path.
    # For a curriculum exercise, asserting "the file exists and parses" is enough.
    result = subprocess.run(
        ["just", "manifest", "lab-test"],
        capture_output=True,
        text=True,
        check=True,
    )
    path = Path(result.stdout.strip())
    assert path.exists()
    assert path.suffix == ".json"

    data = json.loads(path.read_text())
    assert data["versions"]["python"].startswith("3.11")
    assert re.fullmatch(r"[0-9a-f]{40}", data["git_sha"]) or data["git_sha"] == "no-git"
    assert isinstance(data["git_dirty"], bool)
    assert data["seed"] == 42
    assert data["id"].endswith("lab-test")

Por qué este test es "suficientemente bueno" en vez de "perfecto"

Un test perfecto redirigiría la receta a escribir bajo tmp_path. Eso requiere o bien: - Un parámetro --dir en la receta (over-engineering para el currículo). - Un chdir a tmp_path y una receta que resuelva rutas relativas a pwd (ya es el caso — pruébalo).

Cualquiera vale. La versión de arriba es un smoke test; la segunda es un unit test. Elige según cuánta fricción tolerarás.

Checklist de comparación

Al diffear tu versión contra esta, comprueba:

  • ¿Quoteaste {{topic}} correctamente? Un ${topic} sin quotes permite path traversal (../../something).
  • ¿Usaste mkdir -p (sin error si existe) en vez de mkdir?
  • ¿Pusiste git_dirty como booleano real en el JSON (no el string "true")?
  • ¿Manejaste el caso sin-git?
  • ¿Embebiste las versiones como objeto anidado, no como JSON serializado a string?

Si algún "no" — anótalo en learners/borja/phase-00/notes/justfile-notes.md y decide si lo arreglas. A veces la respuesta es "mi forma vale para el currículo" — esa es una conclusión válida.