Descripción
El bootstrap del Agente nunca intenta ejecutar código inexistente. Implementa una cadena de fallback escalonada:
current.txt→ apunta aapp-CURRENT.zip(paquete publicado en el servidor).previous.txt→ apunta aapp-PREVIOUS.zip(versión anterior, guardada tras un update exitoso).- Embebido en el
.exe→ código que viaja en el bootstrap (último recurso).
Si cualquier punto de la cadena falla (paquete corrupto, firma inválida, no existe), el bootstrap baja al siguiente nivel.
Invariante
El bootstrap SIEMPRE tiene código ejecutable — la cadena garantiza que al menos el nivel embebido existe (está en el .exe).
Flujos de ejecución
1. Arranque normal
Lee current.txt
├─ Existe y pasa SHA-256 + Ed25519 → Extrae app-CURRENT.zip, lanza app_main.py
└─ Falla (corrupto / modificado / no existe) → Intenta previous
├─ Existe y pasa verificación → Extrae app-PREVIOUS.zip, lanza
└─ No existe o falla → Extrae código embebido del .exe, lanza
2. Update exitoso
Descarga app-X.Y.Z.zip desde servidor
Verifica SHA-256 + Ed25519
Extrae y prueba (gate --selftest)
Genera puntero: current.txt → app-X.Y.Z.zip
Guarda backup: previous.txt ← app-ANTERIOR.zip (que estaba en current)
Relanza bootstrap
3. Update fallido + rollback
Descarga app-X.Y.Z.zip
Verifica OK
Extrae y lanzan → FALLA en --selftest o en primer arranque
Watchdog detecta (health check fallido)
├─ Reintenta app-X.Y.Z una vez (cubre abort transitorio)
└─ Si reintento falla:
├─ Si existe previous.txt → Restaura current.txt ← previous.txt, relanza
└─ Si NO existe previous.txt → Borra current.txt, cae al embebido
Implementación
Bootstrap (terminal/agent/bootstrap.py)
def load_app_main():
# 1. Try current
if current_exists() and verify_signature(current):
return extract_and_run(current)
# 2. Try previous
if previous_exists() and verify_signature(previous):
return extract_and_run(previous)
# 3. Fall back to embedded
return extract_embedded_and_run()
Updater (core/pkg_updater.py)
- Descarga y verifica.
- Genera puntero atómico (
current.txt). - Guarda backup de anterior (
previous.txt). - Watchdog reintenta + fallback.
Casos de uso que resuelve
| Caso | Sin cadena | Con cadena |
|---|---|---|
| Update con paquete corrupto | Agente muerto, manual fix | Rollback a previous automático |
| AV bloquea paquete en arranque | Muerto | Reintenta, luego previous/embebido |
| Primera transición de embebido a paquete | previous.txt vacío, rollback roto | Borra current.txt, cae a embebido |
| Servidor offline (no puedo actualizar) | Agente esperando update infinito | Ejecuta current o previous normalmente |
| Cliente reinstala desde setup-stub | Código embebido garantizado | ✓ |
Densidad de la cadena
La cadena es más fuerte cuanto más paquetes se han actualizado exitosamente:
- 1ª ejecución (desde setup-stub): solo embebido disponible.
- Tras 1er update exitoso:
current+ embebido (previous vacío). - Tras 2º update:
current+previous+ embebido (máxima fortaleza).
Una máquina que nunca actualiza indefinidamente sigue siendo funcional (embebido).
Testing
tests/agent/test_agent_launcher.py:- Verificación de paquete corrupto → fallback.
- AV bloquea en arranque → rollback.
previous.txtvacío → caída a embebido.- Marker de extracción → idempotencia.
Véase también
- [[feature—agent-lanzador—rollback-silencioso-infalible]]
- [[feature—agent-lanzador—bootstrap-zip]]
- [[feature—agent-lanzador—paquete-firmado-ed25519]]
- [[concept—agent-lanzador—plan-f3]]
- [[incident—20260707—bootstrap-2-14-0-roto]]