Skip to content

Hook de publicación del dump al final de originals_backup

Actualización 21oct, 12:13 CEST: ledger persistido y métricas posteriores al reset corroboran éxito de los cuatro grupos y publicación a las11:24:34, antes del arranque pmx50 de las11:56:40. A las12:22 la consulta con el usuario del daemon confirmó el mismo handle ended/exit0 y próxima ejecución22oct11:10. No se relanzó el trabajo ni se inspeccionaron destinos.

Estado actualizado, 20 octubre 17:33 CEST: publicador v2 instalado a las 17:32:26 y verificado independientemente a las 17:33:50, sin ejecutarlo. Backend, RunWisp, trece archivos de estado, incluido el lock, permanecen iguales. La primera publicación diaria continúa pendiente; instalar el archivo no acredita una copia ni resuelve por sí solo OriginalsDumpPublicationRequired. Instalación, recibos y límites.

Estado histórico02:05CEST: backend y helper instalados, publicación habilitada y configuración cargada en RunWisp. Primera ejecución natural prevista11:10, todavía pendiente. Fuente Ansible reconciliada con las19 tareas y daemon; no se aplicó el role completo.

El backend fue actualizado posteriormente a SHA4aeba3bd… para cancelación segura; el helper no cambió. La suite nativa adicional quedó interrumpida por reinicio, sin resultado; las pruebas locales y la compilación nativa anteriores permanecen acreditadas.

Las secciones siguientes conservan la preparación y las comprobaciones por fases, con sus condiciones y hashes históricos; la activación aplicada figura al final.

Interfaz y comportamiento

La API acordada es publish_existing_dump(report, channel=None). El módulo selecciona y valida un dump ya existente con su propia fecha; el stamp de una reanudación antigua de originales no fecha el dump. El módulo sólo extrae del informe las referencias de photos/pics pertinentes y no genera un nuevo export.

backup_originals.py añade el flag explícito --publish-immich-dump, desactivado por defecto. Sin él no importa ni llama al módulo nuevo. Al habilitarlo, el hook se ejecuta una vez al final del bucle de las cuatro áreas y dentro del flock existente, incluyendo el caso en que todas responden already_complete. --after-current puede combinarse con el flag. No se añade tarea, timer ni copia simultánea: la activación futura corresponde al job existente de las 11:10.

Los únicos resultados aceptados como éxito son published_readback_verified y already_complete_hashes_verified; ambos deben declarar coherence_verified=False y database_restored=False. El hook no eleva la verificación de transporte/readback a coherencia entre base de datos y medios ni a una restauración de Immich.

Se registra dump_publication separado de los cuatro grupos, visible en --status. Un fallo del publisher hace fallar el job, conserva los grupos completados y la última publicación satisfactoria y guarda solamente el nombre de la clase de excepción. Si fallan los originales antes, el publisher no se ejecuta y queda not_run_upstream_error. La contención del lock devuelve el 75 previo sin modificar el informe activo. La detección remota de una publicación ya completada permite reconciliar una ejecución anterior cuyo guardado local hubiera fallado.

Verificación y pendientes

python3 scripts/original-backups/test_backup.py: 25 tests pasan, con 1 omitido porque el transporte concreto rsync 3.2+ no está disponible en el fixture Mac. Los siete tests nuevos cubren default sin llamadas, cuatro grupos existentes y evidencia intacta, exclusión real del flock, ambos estados de éxito, fallo saneado del publisher con conservación del éxito previo, fallo upstream sin publicación y rechazo de resultados sin verificación o con afirmaciones excesivas. Los tests anteriores de muestreo de photos y evidencia legacy siguen pasando.

La prueba integrada usa un publisher simulado: no certifica su implementación ni los permisos del NAS. Antes de activar hacen falta revisión conjunta del módulo, capability/privacidad del receptor y permisos remotos acreditados, instalación del módulo junto al script y cambio explícito del comando del mismo job. Las métricas actuales siguen cubriendo los cuatro grupos; no se afirma que exista ya alerta dedicada para la publicación del dump. El estado disabled no equivale a respaldo diario activo.

SHA256:

  • backup_originals.py: <REDACTED>.
  • test_backup.py: <REDACTED>.

Sin commit ni despliegue. La revisión y publicación de fuentes corresponde a root.

Despliegue autorizado, 20 de octubre a las 00:53 CEST

Root autorizó instalar las fuentes publicadas bc7b1ae/dcd2888, conservando el publisher OFF. Se revalidó VM208: backend previo exacto <REDACTED>, ningún proceso backup/publisher y flock disponible. La instalación volvió a comprobar estas condiciones manteniendo el mismo flock durante todo el cambio.

Se compilaron ambas fuentes en memoria, sin ejecutarlas ni generar pyc. Se prepararon los dos ficheros con fsync y se publicaron mediante rename atómico por fichero, helper primero y backend último. El flock y el flag desactivado impiden que una ejecución autorizada observe un backend habilitado con helper parcial; no se afirma una transacción atómica de dos nombres ante pérdida de energía. Se preservó backend root:root/0755; el helper nuevo quedó root:root/0644. Se sincronizaron directorios de instalación y rollback.

Runtime VM208 SHA256
/home/monxas/scripts/backup_originals.py <REDACTED>
/home/monxas/scripts/immich-dump-publisher.py <REDACTED>
last.json, intacto antes/después <REDACTED>
RunWisp, intacto antes/después <REDACTED>

Rollback privado: VM208:/root/rollbacks/originals-dump-hook-20261019T225317Z, directorio 0700, copia del backend previo y metadata 0600/fsync. El helper no existía anteriormente; la metadata conserva este hecho. Para revertir, exigir de nuevo ausencia de procesos y flock exclusivo, CAS de los hashes instalados, restaurar backend previo mediante temporal/rename conservando root:root/0755 y retirar sólo este helper nuevo si sigue siendo el hash registrado y el publisher continúa desactivado. No borrar estado de copias ni tocar el scheduler. La reversión no se ejecutó.

Validación de sólo lectura tras instalar:

  • --status: los cuatro grupos del stamp 20261019 siguen complete, con muestras 3/3/3/4 para documents/proyectos/photos/pics; dump_publication.enabled=false, estado disabled.
  • --metrics: las cinco métricas originals_dump_publication_* dan cero: enabled, failed, last_success_timestamp_seconds, in_progress y started_timestamp_seconds. No se fabricó primer éxito.
  • La tarea efectiva de /home/monxas/runwisp/runwisp.toml conserva exactamente el comando previo sin --publish-immich-dump, cron 10 11 * * *, catch_up=skip y on_overlap=skip. Sólo se extrajeron estos campos; no se imprimió configuración completa.

Las métricas específicas del dump se añadieron después del primer hook: 29 tests del backend pasan, uno omitido en el entorno Mac. La suite del publisher corregido pasó sus 36 tests, con cancelación/descendientes y presupuestos locales verificados mediante fixtures. El fallo global del job ya existe en RunWisp; las métricas y dos reglas específicas cubren la telemetría que faltaba. No se ejecutó backup, publisher, export SQL ni carga de GPU. Instalar el código no constituye respaldo diario activo: habilitar el flag sigue siendo una fase separada de la coordinación.

Auditoría de persistencia de la tarea nativa

Revisión posterior, de sólo lectura, del 20 de octubre. La tarea originals_backup está declarada directamente en el runtime /home/monxas/runwisp/runwisp.toml, cuyo hash sigue siendo <REDACTED>. Conserva cron 10 11 * * *, timeout/sudo previos, env_base="clean", working_dir="~", catch_up="skip", on_overlap="skip" y ausencia del flag del publisher. La lectura remota extrajo únicamente esos metadatos y comprobó la ubicación de la tarea; no imprimió el fichero completo ni variables de credenciales.

La fuente persistente reproducible está incompleta:

  • scripts/original-backups/runwisp-task.toml conserva la definición correcta, pero el role no consume ese fichero.
  • ansible/host_vars/media-208.yml define 16 tareas nativas y ninguna originals_backup.
  • roles/homelab_tools/templates/runwisp.toml.j2 renderiza sólo esa lista. El render local actual, sin secretos, no contiene la tarea.
  • El daemon incluye runwisp.d/*.toml, pero actualmente hay cero fragmentos en ese directorio; no existe una segunda definición que preserve la tarea al sustituir el fichero principal. El comentario histórico del template que dice que esos fragmentos no se usan está desactualizado respecto de la directiva include, aunque actualmente el directorio esté vacío.

Un ansible-pull automático ordinario no ejecuta este role. ansible/playbook.yml:659–685 lo documenta y etiqueta el play con [homelab_tools, never]. El riesgo acreditado es una aplicación explícita con --tags homelab_tools o --tags homelab_tools_runwisp: tasks/runwisp.yml sustituye el TOML mediante template y, si cambia, hace reload con los paths nativos. Esa operación eliminaría la definición directa de originales mientras la fuente siga incompleta. No se ha ejecutado ni simulado remotamente esa aplicación.

Propuesta mínima, todavía no implementada

Mantener scripts/original-backups/runwisp-task.toml como única definición común del job y hacer que el template la incluya mediante lookup('ansible.builtin.file', playbook_dir + '/../scripts/original-backups/runwisp-task.toml'), detrás de una variable específica homelab_tools_runwisp_originals_backup_enabled con default false y true sólo en host_vars de media-208. Esta variable preservaría la tarea existente; no sería el flag de publicación del dump. El snippet seguiría sin --publish-immich-dump hasta la decisión separada de root tras observar el productor natural.

La propuesta toca únicamente defaults, host_vars media-208 y el final del template, evitando otra copia manual del cron/comando en YAML y otra tarea/cron/plataforma. Un test de render debe exigir una sola sección [tasks.originals_backup], igualdad TOML con el snippet común, default desactivado sin cambios para otros hosts y conservación del resto de tareas/daemon. Debe corregirse también el comentario histórico sobre includes.

Se probó esta inclusión sólo en memoria: render TOML válido con 16→17 tareas, tarea de originales idéntica al snippet, mismo daemon y otras 16 tareas fuente idénticas, sin flag de publicación; con el booleano desactivado, render idéntico al previo. Esto valida la propuesta del template, no su despliegue ni la equivalencia de todo el runtime. No se auditó el resto de tareas runtime ni se autoriza aplicar el role completo con sus posibles cambios ajenos; esa aplicación necesitará un diff propio y controlado. No se modificaron fuentes de Ansible, scheduler, tarea activa ni estado del publisher en esta auditoría.

Implementación fuente autorizada de persistencia

La coordinación autorizó implementar la propuesta anterior sin aplicar el role ni tocar runtime. Se añadió homelab_tools_runwisp_originals_backup_enabled, default false, activado únicamente en host_vars/media-208.yml. Esta variable incluye el job original existente; no activa el publisher.

El template reutiliza el snippet común sin duplicar sus campos, mediante el lookup builtin de fichero con ruta role_path + '/../../../scripts/original-backups/runwisp-task.toml'. La ruta parte del role dentro del checkout, no del directorio de trabajo, del usuario ni de un playbook concreto. Requiere el checkout del repositorio con su directorio scripts; un role copiado aisladamente no tiene esa fuente y debe fallar en vez de inventarla. El snippet no se modificó y conserva su SHA256 <REDACTED>, cron 11:10, timeout/sudo, entorno clean, directorio ~ y skip/skip, sin flag de publicación.

Antes de cualquier modificación de ficheros o servicios del role, un assert exige modo user y ausencia de originals_backup en la lista nativa estándar cuando se incluye el snippet. Si alguien añade la tarea a ambas fuentes, se rechaza la aplicación antes de escribir el config. Se corrigió el comentario obsoleto sobre fragments y se mantuvo explícito que el play lleva never; no cambió esa política de auto-pull.

Validación local: seis tests pasan en scripts/original-backups/test_runwisp_persistence.py. Comprueban render TOML 16→17, igualdad exacta con el fragmento común, daemon y otras 16 tareas intactos, default desactivado en los otros hosts, resolución portable en un checkout temporal reubicado, rechazo de duplicado antes del primer cambio de fichero, rechazo del modo host y conservación del tag never. No hubo lookup de secretos, ejecución de Ansible ni SSH en esta implementación.

Hashes para revisión:

  • Template: <REDACTED>.
  • Test nuevo: <REDACTED>.

El cambio de fuente queda pendiente de revisión/publicación de root. No se ha aplicado todo el role ni se ha reconciliado el resto del runtime. El job activo conserva su configuración previa; la observación del dump natural y la decisión sobre habilitar publicación siguen separadas.

Revisión previa a activación, 01:25 CEST

La activación queda condicionada a ensayo DB-only satisfactorio y observación del productor natural del día20. Cambio previsto: un único flag --publish-immich-dump en el fragmento común y la tarea runtime existente, con parse TOML, comprobación de tarea única, CAS, rollback privado y recarga nativa. No aplicar el role completo, añadir cron ni ejecutar un backup de prueba.

Las cinco métricas reflejan la última ejecución: añadir el flag no acredita el primer ciclo. Sin primer éxito, Stale permanece silenciosa por diseño; comprobar el job natural de las11:10. Retirar el flag tampoco borra la última evidencia.

Los pins locales aún no tienen poda: aproximadamente5,5GB/mes con el dump actual. La reserva libre8GiB hace fallar antes de consumirla; no es una cuota de pins. La retención de cinco dumps del productor no elimina estos pins. Se difiere GC: manifest local se crea antes de publicar y last_success sólo conserva el último éxito. Primero hacen falta recibos durables por generación y protección de consumidores activos; nunca inferir publicación satisfactoria por nombre/mtime. Una eventual poda debe conservar fallos y staging, validar propiedad e identidad sin seguir enlaces, eliminar sólo hojas conocidas y revalidar complete/privacidad/ hashes remotos con consultas limitadas. No se ha borrado ningún pin ni generación.

Preflight de configuración efectiva, 01:33–01:35 CEST

Lectura directa de VM208, sin pasar por pmx50: hash99d73dd9… se mantiene, uid/gid1000:1000, modo0600, 19 tareas runtime y cero fragments; originals_backup única, mismo cron11:10 y comando sin flag. El Python remoto no dispone de tomllib; no se instaló nada: la lectura posterior se capturó en memoria y se analizó con el tomllib local, mostrando únicamente metadatos y la tarea seleccionada. No se imprimió el resto del config ni variables.

El role renderiza16 tareas estándar más originales, pero no coincide con todo el runtime: aparecen qwen_image_backup, git_autocommit y nas-ssh-avisos-limpia, mientras service_routes no aparece como tarea separada en el runtime. Esto no acredita fallo de rutas ni autoriza eliminar tareas. Antes de reconciliar el role completo hay que resolver propietario/fuente de esas diferencias; la activación prevista preservará las otras18 tareas exactamente y no realizará un apply de Ansible.

Procedencia de las tareas y activación preparada, 01:43 CEST

La auditoría local posterior resolvió las diferencias: las tres tareas están versionadas en homelab-stacks/ops/runwisp/runwisp.toml (18 tareas base). Service_routes fue retirada deliberadamente en3b6392f, sustituida por el timer CT212 rutas-smoke-diario y sus alertas; no es una pérdida demostrada. El TOML base más el snippet de originales corresponde a las19 tareas runtime. No copiar todo el TOML a Ansible: perdería las guardas recientes de AudioMuse. El role nativo aún necesita reconciliar las tres definiciones y retirar routes antes de un apply completo; no se ha aplicado. El README antiguo documenta un symlink, pero la comprobación runtime de esta ronda no sustituye evidencia por esa descripción histórica.

El dump natural20 terminó sin intervención: a01:41 final regularUID1000/0600, 183629054bytes, mtime01:31:41; cero temporales y sin procesos pg-dumps.sh. No se leyó ni exportó la BD para esta comprobación.

Fuente del snippet preparada con un único flag --publish-immich-dump; seis pruebas de persistencia pasan. La configuración runtime continúa sin flag hasta revisar y aplicar el cambio CAS con rollback y recarga nativa.

Activación aplicada y verificada, 01:48–01:49 CEST

Fuente publicada en e4d4cfb, helper1df82739… revisado con13 pruebas más ensayo independiente de cancelación/rollback. Se instaló en stage privado VM208 ~/.local/state/immich-dump-enable/20261020-0151, fuente hash idéntico, y se ejecutó primero plan sin cambios. El candidato se analizó además con tomllib local: 19 tareas, las otras18 y todos los campos ajenos al único run permanecen idénticos; coincide con el snippet común.

CAS del baseline99d73dd9… y prerequisitos instalados/productor superados. Único cambio: añadir --publish-immich-dump al run existente. Config nuevo SHA256<REDACTED>, uid/gid1000:1000/modo0600/fichero regular. Backup y recibo privados/fsync en /home/monxas/runwisp/rollbacks/dump-publication-q8_uhj04; original.toml600, directorio700. Recarga nativa con CONFIG/DATA/socket explícitos, rc0; sin restart.

Verificación RPC status --json a01:48:59: healthy=true, scheduling_active=true, config_stale=false, timezoneEurope/Madrid, 19tareas y una originals_backup con cron11:10/próxima ejecución20oct11:10. El estado previo de originales sigue SHA256<REDACTED>. No se disparó job, exportación, upload, copia ni métrica artificial.

La primera publicación automática queda pendiente de la ejecución natural. Las métricas aún reflejan el ciclo anterior, con publisher desactivado entonces; no cambiarlas para adelantar un éxito. Si hay que revertir, restaurar sólo el original.toml bajo CAS del hash63af…, recargar con los mismos paths y retirar el flag en fuente; conservar ledger, pins y generaciones remotas.

Paridad de fuente reconciliada, 01:52 CEST

Se importaron literalmente las tres definiciones versionadas de Gitstacks en host_vars media208 y se retiró service_routes ya sustituida. Las guardas de AudioMuse y el snippet común de originales permanecen. Una opción por tarea conserva los campos omitidos de NAS; no se agregan defaults a esa tarea.

Ocho pruebas pasan independientemente. El render completo de ambos Proxmox conserva la misma semántica. La comparación adicional del coordinador, con el TOML runtime capturado únicamente en memoria, da19/19 tareas, ninguna exclusiva de fuente/runtime, ninguna diferencia de campo y ninguna diferencia del daemon. No se ejecutó Ansible ni se modificaron tareas durante esa reconciliación. Queda así resuelto el riesgo documentado de eliminar tres jobs o reactivar la ruta retirada al regenerar el TOML; no acredita otros efectos del role completo.

Identidad de publicación v2: contextos distintos, sólo fuente20oct

Esta sección conserva la fase de preparación; la instalación posterior se documenta al final.

Una fixture local demuestra una colisión de identidad v1: mismo dump, bytes, fecha y mtime con referencias de originales19 y después20 produce el mismo dFECHA-SHA256_DEL_DUMP. El segundo pin aborta por manifiesto distinto. El reintento exacto sí era idempotente y el primer artefacto permanecía intacto; no se ha atribuido este caso al fallo real11:10, que fue el protocolo rsync.

La fuente v2 calcula el sufijo SHA256 sobre JSON canónico de todos los campos inmutables del manifiesto salvo generation: versión, dumpSHA/fecha/nombre/bytes/ mtimeUTC, referencias y flags de verificación. Ordena las claves, también las de referencias. Sigue midiendo74caracteres y cumple la gramática existente del receiver; no necesita desplegar un protocolo NAS nuevo. El sufijo v2 ya no debe interpretarse como hash del dump: éste sigue explícito en dump_sha256.

Un pin v1 local válido se reutiliza sólo si todos sus campos coinciden con el contexto actual. Otro contexto válido conserva ese pin y crea uno v2 distinto. Un pin v2 exacto se reutiliza sin reescribirlo. Los pins legacy descubiertos se validan también si ya existe v2; corrupción, formato desconocido, claves extra, symlinks o metadatos inseguros hacen fallar cerrado. publish valida ambas versiones antes de usar el canal. La primera generación no pierde bytes ni inodos.

No se deduplican publicaciones globalmente: si desapareció el pin local pero permanece una generación v1 sólo en NAS, este cambio no la descubre ni descarga su manifiesto para reutilizarla. Puede crear otra copia de aproximadamente183MB para el mismo dump y un contexto distinto; tampoco añade --link-dest, -H, poda o retención. Cambiar mtime puede cambiar el ID aunque el contenido no cambie. Las referencias conservan su significado contextual: todos los flags de coherencia/restauración siguen en false, sin acreditar integridad de los medios.

El ensayo operativo sigue pendiente: el parche del receiver11:28 no relanzó el ciclo fallido11:10. El fragmento runwisp-task.toml mantiene el tick diario11:10, sin nuevo horario/reintento; después del fallo del20, su próximo tick ordinario es21oct11:10. El requisito del20oct16:30CEST conserva for: 10m: puede avisar tras la primera evaluación posterior al plazo más10min, no garantiza una notificación a16:40exactos. Esa alerta no ejecuta recuperación. No se cambia el plazo, hold, scheduler ni métricas para cubrir esta deuda; un éxito requiere un intento real con copia de originales, publicación y verificación correspondientes.

Esta revisión sólo ejecuta fixtures locales sintéticos. No lee dumps reales, fotos, NAS, métricas ni status; no inicia jobs ni instala v2. La publicación del código no acredita un incremental posterior correcto ni una publicación nueva.

Validación local: 55 pruebas del publicador, 58 del backend (una omitida porque requiere GNU rsync en Linux) y 38 del receiver pasan. Gramática Python3.10 y git diff --check correctos. No se han ejecutado pruebas remotas.

Instalación protectora v2: 17:32–17:33 CEST

La lectura pasiva de las 17:26:09.690718 CEST confirmó el publicador v1 3d704db2…, root:root0644, backend 8581a6dc…, RunWisp f7b3a3ad…, trece archivos de estado estables y cero procesos de copia, lectores o publicación, sin cmdlines ilegibles. Se conservaron el recibo original y su proyección JSON canónica; ésta fija el perfil SHA256 <REDACTED>. Es una observación puntual, no exclusión global contra escritores no cooperantes.

Root ejecutó una vez el instalador revisado a las 17:32:26.531 CEST: SSH rc0 y stderr vacío. Sólo reemplazó /home/monxas/scripts/immich-dump-publisher.py por la fuente publicada v2, SHA256 <REDACTED>. Preservó root:root0644 y nlink1; archivo final de 23324 bytes, inodo823552. El instalador retuvo el flock existente, comprobó CAS de archivo y controles, compiló sin importar ni ejecutar el módulo y usó rename/fsync. El candidato permaneció ligado a su FD, identidad y SHA antes del rename; no se borró un temporal ajeno por nombre. No se cambió receptor, NAS, backend o scheduler.

Backup privado: /root/rollback-originals-publisher-identity-20261020T153226Z-3dtpahv9. El recibo local de instalación tiene SHA256 <REDACTED>. La lectura independiente de las 17:33:50.950775 CEST acreditó el nuevo hash y su identidad contra el recibo, compilación únicamente en memoria, backup root0700 y publisher.before root0600 con el SHA v1 exacto. El recibo nativo root0600 tiene fase installed y SHA256 <REDACTED>.

La proyección independiente privada tiene SHA256 <REDACTED>; confirmó el mismo boot de VM208, backend y RunWisp, los trece archivos de estado y la identidad/hash del lock incluido sin cambios en dos lecturas. No se importó el publicador ni se llamó su CLI, backend, métricas, NAS o tarea. Los 27 tests sintéticos del instalador pasan con Python3.12 y3.14; sintaxis3.10 y plan sin red correctos.

Esta versión evita la colisión reproducida entre contextos distintos. No repara el ciclo fallido de las 11:10 ni acredita primera publicación: el requisito vencido observado a las 16:41 sigue pendiente de un éxito real, sin nueva consulta que afirmase otro estado. No se relanzó el job; horario, flag y alertas siguen intactos. La próxima ejecución ordinaria continúa prevista para el 21 de octubre a las 11:10, sujeta a disponibilidad y controles, sin garantía de éxito.

Recuperación manual: bajo el mismo lock existente y sin procesos mutadores, exigir CAS del nuevo hash/identidad antes de restaurar sólo publisher.before mediante temporal y rename, preservando root:root0644. Conservar estados, manifiestos, generaciones, pins, configuración y horarios. No hay rollback ni reintento automático ante resultado incierto; este procedimiento no elimina carreras con escritores no cooperantes ni resuelve los reinicios del host.

Validación posterior de protocolo en CI: 17:46–17:48 CEST

La fuente instalada del publicador v2 sigue siendo a3acd931…; no se reemplazó ni ejecutó durante esta fase. Se amplió sólo el test del receiver incremental para ejercer GNU rsync real, argv separado y reutilización de hardlinks sobre generaciones locales temporales. CI39451463152 pasó en Python3.10 y3.12: 39 tests del receiver y12 de recuperación, sin skips. Los55 tests del publicador ya acreditados permanecen una validación distinta; los38 del receiver registrados en fases anteriores conservan su fecha y alcance. Detalles del nuevo contrato. No hubo publicación, acceso NAS, copia de producción ni cambio de horarios: primera publicación y siguiente incremental real siguen pendientes de prueba.

Ciclo ordinario del 21 de octubre, 11:10: generación retomada

La fuente y el RPC del scheduler acreditan cron 10 11 * * *, Europe/Madrid, con publicación Immich habilitada, entorno limpio y políticas skip/skip. El trabajo 01M5EVJZJ197C2JJW55BGER204 comenzó por cron a las 11:10:00,001336 CEST. A las 11:16:15 seguía running: no se relanzó ni se atribuye éxito global a un exit_code provisional mientras está activo. El scheduler estaba sano, scheduling activo y sin deriva de configuración.

Grupo Último resultado nuevo observado
Documentos COMPLETE y éxito 21oct11:10:19,711714
Proyectos COMPLETE y éxito 21oct11:10:42,898533
Photos COPYING desde 11:11:14,537118; heartbeat fresco 11:16:15,123851
Pics Sin inicio nuevo en ese ledger; conserva éxito histórico 20oct00:30:08
Publicación del dump enabled=true, pending, sin inicio ni éxito nuevos

Se está retomando la generación 20261020, no creando ficticiamente una 20261021. Prometheus, productor 11:15:02, confirma nuevos last_success de documentos/proyectos y process_running=1. Photos_last_success sigue a cero; el indicador publisher in_progress=1 incluye pending y no demuestra que esté exportando ya. Los éxitos de dos grupos no prueban coherencia final de toda la generación, recuperación completa de Immich ni publicación terminada.

Guardas finales hasta 11:17:14 CEST: VM208 running en pmx50, parent boot c4c64375…, guest <REDACTED_UUID>, sin cambios entre capturas. Sólo RPC, ledger local e información ya recogida en Prometheus; ningún acceso al NAS ni job manual. Primer lector incompatible con tomllib conservado como fallo del lector, no del scheduler.

Recibos privados 0600 bajo directorio 0700 ~/.local/state/originals-natural-20261021-61qeh4zb/:

Archivo SHA256
scheduler-poll.json <REDACTED>
ledger-poll.json <REDACTED>
metrics-poll.json <REDACTED>
NEXT.json <REDACTED>

La siguiente observación debe consultar ese mismo handle y su heartbeat, o su resultado terminal si ya terminó. Una ausencia temporal de observación no justifica relanzarlo; próxima ejecución según esa lectura previa: 22oct11:10.

Recuperación observada tras el reset: 21oct, 12:11–12:13 CEST

Nueva observación de sólo lectura: pmx50 boot 558fbd3c…, propietario de VM208 proxmox/qemu/running y guest boot <REDACTED_UUID> estables antes/después. La generación retomada conserva estos éxitos:

Etapa COMPLETE / last_success CEST, 21oct
Documentos 11:10:19,711714
Proyectos 11:10:42,898533
Photos 11:21:40,047501
Pics 11:22:44,367048
Publicación dump 11:24:34,426590

Prometheus leído a las12:12:03,856 con productor12:10:02 corrobora los tiempos: collection_success=1, process_running=0, cuatro grupos copying=0/failed=0 y publicación failed=0/in_progress=0. Acredita ledger y métricas persistidos, no nueva lectura de destinos/manifiestos, integridad de todos los bytes, coherencia completa dump/originales ni restauración de Immich.

El primer lector RPC terminó rc1; el segundo wrapper SSH/Python terminó0, pero su JSON válido registra rc1 del CLI de estado, con boots estables. No se conservaron stdout/stderr del CLI; no permite distinguir daemon, socket, configuración o CLI. El primer comprobador tiene además una variable local sin inicializar ante rc no cero que puede enmascarar el resultado; no se atribuye a ella el primer fallo sin una traza. Estado terminal del handle y calendario actual UNKNOWN: no se deduce interrupción, éxito terminal del scheduler ni fallo del daemon por ese retorno. No se relanzó ningún job ni se forzó productor, copia o consulta NAS. La siguiente tarea es resolver el lector y verificar el estado, sin repetir la copia ya declarada completa.

Recibo privado ~/.local/state/originals-natural-20261021-61qeh4zb/recovery-epoch558-hrbuv0n9/recovery-assessment.json, SHA256 <REDACTED>. Ledger SHA256 <REDACTED>; Prometheus SHA256 <REDACTED>.

Cierre de la consulta RPC, 21oct a las 12:22 CEST

Comparación nativa controlada, sin cambios: mismo ejecutable RunWisp0.14.0 (SHA256 <REDACTED>), flags, entorno limpio y cwd. Como root devuelve CLI1, JSON healthy=false y cero tareas; como monxas, usuario del daemon, CLI0, healthy=true, scheduling_active=true y config_stale=false. La unidad permaneció activa, PID834, NRestarts0, User monxas; socket1000:1000/0600. Boots y propietario permanecieron estables antes/después. La diferencia de contexto UID explica la diferencia observada; no se identifica la capa exacta que rechaza root.

El handle original 01M5EVJZJ197C2JJW55BGER204, sin cambiarlo por otro, figura ended, exit_code0, failed=false, missed=false. Conserva cron 10 11 * * *, timezone Europe/Madrid y próxima ejecución 22oct11:10. La incertidumbre del apartado anterior queda cerrada por esta lectura, sin afirmar integridad de destinos o restore completo. La consulta correcta no reinició daemon, recargó configuración ni ejecutó tareas.

Para próximas observaciones, ejecutar el CLI como el usuario nativo del daemon y mantener CONFIG/DATA/socket explícitos. Proyectar sólo campos necesarios en memoria; su salida completa contiene configuración de tareas y no debe imprimirse. Usar root para leer el ledger privado es una operación distinta y no justifica ejecutar el cliente RPC con ese contexto.

Recibo ~/.local/state/runwisp-native-ro-epoch558-k5m27s33/receipt.json, SHA256 <REDACTED>.