Repository navigation
feat(wsl): adaptador WSL (Fase A+B) + lab W3 de evidencia — issue #90 - #220
Conversation
El workflow w3-evidence.yml vive en w2-fase-a; GitHub solo ofrece para dispatch los workflows presentes en la rama por defecto del repo. Se añade aquí (mismo contenido, sin cambios) para que aparezca en el desplegable de "Run workflow" del fork y se pueda lanzar con ref=w2-fase-a. Co-Authored-By: Claude Code <noreply@anthropic.com>
El run falló en el primer paso: `windows-latest` es ahora la imagen windows-2025-vs2026, sin WSL instalado. Las imágenes con WSL preinstalado son windows-2022 y windows-2019 (el lab W0 corrió en la que entonces era latest). Se fija windows-2022 y se añade una comprobación explícita de WSL al inicio, para que su ausencia se reporte con un mensaje claro en vez de un exit code opaco. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: [CC] <noreply@anthropic.com>
…ev6#90 Trasplanta a main el contrato `WslAdapter` y su implementación real contra `wsl.exe`, junto con el lab W3 que lo acredita con WSL real: - src-tauri/src/wsl/: Fase A (mod.rs contrato + mock determinista, errors.rs) y Fase B (real.rs RealWslAdapter, process.rs lanzador con cancelación acotada nivel 1). Solo depende de cli_process + std. - lib.rs: expone `pub mod wsl`. - src-tauri/examples/wsl-wslenv-probe.rs: harness que mide WSLENV por el lanzamiento real del adaptador (run_command -> env_with -> ProcessRunner con env_clear().envs()); no es un binario del producto. - .github/workflows/w3-evidence.yml + scripts/wsl-f14-probe.rs: el lab de evidencia — Gap 1 (pidfile endurecido, kill acotado, testigo ajeno), Gap 1b (timeout del host), Gap 2 (F14 doble frontera por la vía real) y Gap 3 (WSLENV por el adaptador real, allowlist x ramas + no-duplicar). El lab ejercita el código real y en WSL real destapó y corrigió un bug de shell_strategy (exec no ejecuta builtins -> SCRIPT_COMMAND_V). Incluye el Caso I estricto (debe escalar a KILL) y el contrato nonpath=ausente. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
@sosan gracias. He revisado el head Resuelto en
|
…nes) en feat/wsl-adapter Trae a la rama de la PR el main vigente de egdev6/quorb (f3897bc, incluye egdev6#207 y egdev6#219). docs/i18n-inventory.md se regenera desde el árbol fusionado en un commit aparte para que el header refleje el head real. Co-Authored-By: Claude Code <noreply@anthropic.com>
1. shell_strategy no confunde error WSL con ausencia de Bash: `control_raw` devuelve el output crudo, así que se clasifica con `errors::classify`: si la salida contiene un token WSL reconocido (p. ej. WSL_E_DISTRO_NOT_FOUND porque la distro desapareció entre la enumeración y la sonda), se propaga el error real en vez de caer a `ShPosix` como estrategia válida. El fallback a `ShPosix` queda solo para el caso normal `command -v` sin bash (salida vacía, sin token). Regresión: shell_strategy_propagates_wsl_distro_loss_not_sh_posix. 2. kill_remote_process no escala tras observar la muerte: si una sonda `kill -0` ve el PID muerto, se rompe y NO se vuelve a comprobar ni se envía KILL (un `kill -0` posterior podría devolver 0 porque el PID fue reutilizado por otro proceso — señalizar al proceso equivocado). La identidad de la instancia se protege observando la desaparición, no volviendo a preguntar por el número. Test determinista case_disappears_then_reappears: PID que desaparece y reaparece con otra identidad → no recibe KILL. 3. session_path usa semántica del GUEST (POSIX), no del host Windows: `Path::is_absolute()` en Windows devuelve false para `/home/u/...` y true para `C:\...`, justo al revés del guest. Nuevo helper `is_guest_absolute` (empieza por `/`) y tests: una ruta Windows explícita se rechaza en modo WSL, y `/etc/passwd` como relativa del proveedor se rechaza aunque en Windows no sea "absoluta". docs/i18n-inventory.md regenerado tras integrar main del upstream (incluye egdev6#207 y egdev6#219) y los cambios de este commit. Co-Authored-By: Claude Code <noreply@anthropic.com>
El gate compara el hash del árbol `src-tauri/src/` con el declarado, así que los cambios de los 3 hallazgos lo dejaban desactualizado (aunque los textos no hubieran cambiado). Regenerado desde el árbol actual. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
@egdev6 aplicados los 3 hallazgos de tu revisión y regenerado el inventario. CI completo en verde sobre el head final y W3 re-ejecutado sobre ese mismo head. Hallazgos aplicados (commit
|
| Check | Resultado |
|---|---|
| Tests and build (Windows) | ✅ pass |
| Tests and build (Linux) | ✅ pass |
| Tests and bundle (macOS ARM64) | ✅ pass |
| Secrets scan | ✅ pass |
| Static code safety | ✅ pass |
W3 re-ejecutado sobre este head (cb075d1): run 41123308916 → ✅ success. Artefacto w3-evidence.
GAP1: PASS
GAP1B: PASS
GAP2: PASS (24/24)
GAP3: PASS (26/26; 13 por distro)
VERDICT GLOBAL: PASS
Tests del módulo WSL en local: 73 passed / 0 failed.
|
@sosan, revisé el head Quedan dos puntos antes de integrar:
Mantendría la PR sin integrar hasta resolver ambos puntos. Esta revisión fue estática más contraste de evidencia remota, sin ejecutar código de la PR. |
… i18n) en feat/wsl-adapter El main avanzó con la corrección de colocación X11 (egdev6#221), la notarización resumible de macOS (egdev6#219) y el proveedor Antigravity (egdev6#207). Se integra sobre la rama del adaptador WSL; docs/i18n-inventory.md se regenera desde el árbol fusionado. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Gracias por seguir avanzando con esto, @sosan. Revisé el head Siguen abiertos los dos puntos de mi revisión del 31/10:
El alcance creció sin actualizar la PR. Los commits nuevos añaden Para la PR de #91, un punto a tener en cuenta: Además: la rama tiene conflictos con Esta revisión fue estática sobre el head indicado; no ejecuté el código de la PR. ¡Gracias! |
…ev6#91) Rama de testeo: mismo alcance que ci.yml/linux-pr-ci.yml (cargo test --locked, npm test, inventario i18n, build) más cargo clippy -- -D warnings, que el CI normal no corre. No entra en la PR egdev6#220. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ev6#91) Rama de testeo: mismo alcance que ci.yml/linux-pr-ci.yml (cargo test --locked, npm test, inventario i18n, build) más cargo clippy -- -D warnings. No entra en la PR egdev6#220. Co-Authored-By: Claude Code <noreply@anthropic.com>
Rama de testeo: mismo alcance que ci.yml/linux-pr-ci.yml (cargo test --locked, npm test, inventario i18n, build) más cargo clippy como informe (no gate: el repo tiene lints preexistentes y su CI no corre clippy). No entra en la PR egdev6#220. Co-Authored-By: Claude Code <noreply@anthropic.com>
Rama de testeo: mismo alcance que ci.yml/linux-pr-ci.yml (cargo test --locked, npm test, inventario i18n, build) más cargo clippy como informe (no gate: el repo tiene lints preexistentes y su CI no corre clippy). No entra en la PR egdev6#220. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Gracias por seguir avanzando, @sosan. Seguimos revisando el proceso y los cambios: en el head Aun así, el trabajo todavía no está completo para integrar. Siguen pendientes los puntos de la revisión anterior:
Los checks verdes no sustituyen estas correcciones. Cuando estén aplicadas, revisaremos el nuevo head y la evidencia W3 correspondiente a ese mismo SHA. Por ahora mantenemos la PR sin integrar. Esta revisión fue estática y de la CI remota; no ejecuté el código de la PR. ¡Gracias por continuar con ello! |
Revisión del mantenedor (2026-11-02), dos puntos: 1. Identidad del proceso. El pidfile guardaba solo el número de PID; entre leerlo y el primer kill, un PID reutilizado por otro proceso de la distro habría recibido TERM/KILL ajeno. Ahora la instancia publica un TOKEN (sufijo aleatorio del directorio) en `token` y el proceso final lo hereda como `QUORB_WSL_TOKEN` vía `exec`. Antes de señalar se comprueba que `/proc/<pid>/environ` contiene el token (fail-closed: identidad no verificada -> no se señala; recuperación explícita del nivel 2). Con regresión: identidad cambiada sin que ninguna sonda observe el PID ausente -> no TERM, no KILL. 2. Rutas con semántica del host. `session_path` componía con `Path::join`, que en Windows introduce `\` en una ruta que viaja literal al `cat` de la distro (Linux): allí `\` no separa nada y se buscaría otro fichero. Ahora se compone con separador `/` (join_guest_path) y se rechazan con `InvalidArgument` las rutas explícitas o relativas que lleven `\`. Tests que fijan el argumento exacto en cualquier plataforma. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
600033c to
232d64a
Compare
|
Gracias por los cambios, @sosan. Revisé el head Queda un bloqueo en la escalada a KILL. En El caso pendiente es: la identidad inicial es correcta → se envía TERM → el proceso original muere y su PID se reutiliza entre sondas → todas las sondas siguientes ven el PID vivo → se envía KILL al proceso nuevo. El test añadido cubre una identidad incorrecta desde el inicio, pero no ese cambio durante la espera. Hace falta proteger también la escalada y añadir una regresión determinista donde la identidad cambie después de TERM sin que ninguna sonda observe el PID ausente: el proceso nuevo no debe recibir KILL. Además, la comprobación y el envío de la señal deben quedar lo más estrechamente ligados posible; una comprobación separada no elimina por sí sola la carrera entre verificar y señalizar. No encontré una ejecución W3 sobre este SHA; cuando esté aplicada la corrección, necesitamos esa evidencia sobre el head final. Todavía no la aprobaría por este punto, pero el alcance y la composición de rutas quedan resueltos. Esta revisión fue estática y de evidencia remota; no ejecuté el código de la PR. ¡Gracias por continuar con el trabajo! |
Rama de testeo: mismo alcance que ci.yml/linux-pr-ci.yml (cargo test --locked, npm test, inventario i18n, build) más cargo clippy como informe (no gate: el repo tiene lints preexistentes y su CI no corre clippy). No entra en la PR egdev6#220. Co-Authored-By: Claude Code <noreply@anthropic.com>
# Conflicts: # docs/i18n-inventory.md
Rama de testeo: mismo alcance que ci.yml/linux-pr-ci.yml (cargo test --locked, npm test, inventario i18n, build) más cargo clippy como informe. No entra en la PR egdev6#220. Co-Authored-By: Claude Code <noreply@anthropic.com>
…6#90) Bloqueo de la revisión del mantenedor (2026-11-03): la identidad se comprobaba antes del TERM, pero durante la espera solo se consultaba `kill -0` y el KILL final se enviaba sin volver a comprobar. Si el proceso original moría y su PID se reutilizaba entre sondas, el nuevo dueño recibía el KILL. La comprobación y el envío van ahora en UNA sola invocación (`SCRIPT_SIGNAL_IF_IDENTITY`: `tr … | grep -qxF … && kill -<señal> <pid>`), también para la escalada: no hay proceso de verificación separado que deje ventana entre verificar y señalizar, y si el PID fue reutilizado el grep falla y no se envía nada (fail-closed). Regresión determinista añadida: identidad correcta → TERM → el original muere y el PID se reutiliza entre sondas (todas las `kill -0` ven vivo) → la escalada re-verifica y el proceso nuevo NO recibe KILL. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Rama de testeo: mismo alcance que ci.yml/linux-pr-ci.yml (cargo test --locked, npm test, inventario i18n, build) más cargo clippy como informe. No entra en la PR egdev6#220. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
¡Aprobada, @sosan! Gracias de verdad por todo el trabajo. Esta PR ha sido un recorrido largo y exigente, y la forma en que la llevaste merece reconocimiento. Cada vuelta de revisión volvió con la corrección aplicada, con el caso exacto cubierto y con evidencia en lugar de afirmaciones. El lab W3 ha sido un aporte enorme: no se limitó a acreditar lo que ya estaba escrito, sino que destapó el fallo de Todo queda verificado sobre este head
Un par de notas para tener presentes, sin que afecten a la aprobación. Queda una ventana mínima entre Gracias otra vez por la paciencia, la constancia y el nivel de detalle. Da gusto revisar trabajo así. Esta revisión fue estática más contraste de CI y W3; no ejecuté el código de la PR. |
* ci(wsl): registrar el lab W3 en main para poder despacharlo El workflow w3-evidence.yml vive en w2-fase-a; GitHub solo ofrece para dispatch los workflows presentes en la rama por defecto del repo. Se añade aquí (mismo contenido, sin cambios) para que aparezca en el desplegable de "Run workflow" del fork y se pueda lanzar con ref=w2-fase-a. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): usar windows-2022 (windows-latest ya no trae WSL) El run falló en el primer paso: `windows-latest` es ahora la imagen windows-2025-vs2026, sin WSL instalado. Las imágenes con WSL preinstalado son windows-2022 y windows-2019 (el lab W0 corrió en la que entonces era latest). Se fija windows-2022 y se añade una comprobación explícita de WSL al inicio, para que su ausencia se reporte con un mensaje claro en vez de un exit code opaco. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): windows-latest + sonda --version + exit 0 (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): sha256 a fichero (PS7 .Content es byte[]) — igual a la rama Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): veredicto global + evidencia always() (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): volcado del import al log (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): import con directorio creado (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): Gap 1 con testigo (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): senal de fichero + .wslconfig (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): configurar WSL del runner (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): shift antes del exec (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): background + EXIT aparte (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): Start-Job para el launch (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): sleep 1 + diagnostico en vivo (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): background dentro de la distro (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): redireccion en una linea (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * feat(w3-lab): Gap 1b (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): script a fichero en Gap 1b (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): vmIdleTimeout=-1 (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): veredicto global solo por Gap 2 (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): Gap 1b continue-on-error (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): Stop-Process tolerante (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): caso empty como string (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * feat(w3-lab): probar Ubuntu con systemd (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * feat(w3-lab): Ubuntu por wsl --install (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): desactivar OOBE de Ubuntu (igual a la rama) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(w3-lab): restaurar list-after (igual a la rama) Co-Authored-By: [CC] <noreply@anthropic.com> * feat(wsl): adaptador WSL (Fase A+B) + lab W3 de evidencia — issue #90 Trasplanta a main el contrato `WslAdapter` y su implementación real contra `wsl.exe`, junto con el lab W3 que lo acredita con WSL real: - src-tauri/src/wsl/: Fase A (mod.rs contrato + mock determinista, errors.rs) y Fase B (real.rs RealWslAdapter, process.rs lanzador con cancelación acotada nivel 1). Solo depende de cli_process + std. - lib.rs: expone `pub mod wsl`. - src-tauri/examples/wsl-wslenv-probe.rs: harness que mide WSLENV por el lanzamiento real del adaptador (run_command -> env_with -> ProcessRunner con env_clear().envs()); no es un binario del producto. - .github/workflows/w3-evidence.yml + scripts/wsl-f14-probe.rs: el lab de evidencia — Gap 1 (pidfile endurecido, kill acotado, testigo ajeno), Gap 1b (timeout del host), Gap 2 (F14 doble frontera por la vía real) y Gap 3 (WSLENV por el adaptador real, allowlist x ramas + no-duplicar). El lab ejercita el código real y en WSL real destapó y corrigió un bug de shell_strategy (exec no ejecuta builtins -> SCRIPT_COMMAND_V). Incluye el Caso I estricto (debe escalar a KILL) y el contrato nonpath=ausente. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): corregir borrow de runner en test, retoques de review y i18n (#90) - real.rs: el test shell_strategy_uses_command_v_script_not_exec movía `runner` en `adapter(runner)` y luego leía `runner.calls()` (E0382 del CI). FakeRunner ya deriva Clone (Arc<Mutex>), así que se pasa `runner.clone()` y el test conserva el acceso a las llamadas reales. - real.rs: eliminar `let mut args = args;` redundante en list_distributions_uses_verbose_list_without_distro. - wsl-wslenv-probe.rs: documentar en parse_env la limitación del salto de línea en el formato `env` (valores con `=` se conservan enteros). - docs/i18n-inventory.md: regenerado (el módulo wsl/ añade textos i18n). Hallazgo de revisión descartado tras verificación: `exec "$@"` con `$@` entrecomillado NO re-evalúa $(...) en los posicionales (confirmado en shell real); no hay inyección por variables de entorno, y read_file usa run_scoped a propósito para conservar la cancelación acotada. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): aplicar los 3 hallazgos de la revisión PR #220 1. shell_strategy no confunde error WSL con ausencia de Bash: `control_raw` devuelve el output crudo, así que se clasifica con `errors::classify`: si la salida contiene un token WSL reconocido (p. ej. WSL_E_DISTRO_NOT_FOUND porque la distro desapareció entre la enumeración y la sonda), se propaga el error real en vez de caer a `ShPosix` como estrategia válida. El fallback a `ShPosix` queda solo para el caso normal `command -v` sin bash (salida vacía, sin token). Regresión: shell_strategy_propagates_wsl_distro_loss_not_sh_posix. 2. kill_remote_process no escala tras observar la muerte: si una sonda `kill -0` ve el PID muerto, se rompe y NO se vuelve a comprobar ni se envía KILL (un `kill -0` posterior podría devolver 0 porque el PID fue reutilizado por otro proceso — señalizar al proceso equivocado). La identidad de la instancia se protege observando la desaparición, no volviendo a preguntar por el número. Test determinista case_disappears_then_reappears: PID que desaparece y reaparece con otra identidad → no recibe KILL. 3. session_path usa semántica del GUEST (POSIX), no del host Windows: `Path::is_absolute()` en Windows devuelve false para `/home/u/...` y true para `C:\...`, justo al revés del guest. Nuevo helper `is_guest_absolute` (empieza por `/`) y tests: una ruta Windows explícita se rechaza en modo WSL, y `/etc/passwd` como relativa del proveedor se rechaza aunque en Windows no sea "absoluta". docs/i18n-inventory.md regenerado tras integrar main del upstream (incluye #207 y #219) y los cambios de este commit. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras los cambios de src-tauri/src El gate compara el hash del árbol `src-tauri/src/` con el declarado, así que los cambios de los 3 hallazgos lo dejaban desactualizado (aunque los textos no hubieran cambiado). Regenerado desde el árbol actual. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): identidad de instancia en el pidfile y rutas POSIX (#90) Revisión del mantenedor (2026-11-02), dos puntos: 1. Identidad del proceso. El pidfile guardaba solo el número de PID; entre leerlo y el primer kill, un PID reutilizado por otro proceso de la distro habría recibido TERM/KILL ajeno. Ahora la instancia publica un TOKEN (sufijo aleatorio del directorio) en `token` y el proceso final lo hereda como `QUORB_WSL_TOKEN` vía `exec`. Antes de señalar se comprueba que `/proc/<pid>/environ` contiene el token (fail-closed: identidad no verificada -> no se señala; recuperación explícita del nivel 2). Con regresión: identidad cambiada sin que ninguna sonda observe el PID ausente -> no TERM, no KILL. 2. Rutas con semántica del host. `session_path` componía con `Path::join`, que en Windows introduce `\` en una ruta que viaja literal al `cat` de la distro (Linux): allí `\` no separa nada y se buscaría otro fichero. Ahora se compone con separador `/` (join_guest_path) y se rechazan con `InvalidArgument` las rutas explícitas o relativas que lleven `\`. Tests que fijan el argumento exacto en cualquier plataforma. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras los mensajes POSIX/identidad Co-Authored-By: Claude Code <noreply@anthropic.com> * feat(wsl): resolver sesiones por el adaptador — SessionResolver (#91) El puente del camino 1: un resolvedor que sabe DÓNDE vive la sesión de un proveedor según el entorno (host Windows/macOS vs distro WSL) y CÓMO se lee — con `read_file` del adaptador (`wsl cat` con cancelación acotada) en WSL, o del disco local en host. Los proveedores no conocen WSL. Contrato de sesiones (#91): - Ruta explícita con prioridad; una inválida es error sin fallback. - Sin credenciales en el entorno elegido, nunca se usa una cuenta de otro entorno silenciosamente. - Errores tipados: Absent (necesita login), Unreadable, Invalid y Wsl (el fallo es del subsistema o la distro, no de la sesión) — la UI distingue "login" de "WSL roto". - No se copian tokens: cada entorno lee en su casa. Tests deterministas con MockWslAdapter: lectura en host y en distro, ausente en cada entorno, y fallo del subsistema propagado como WslError. 80 tests del módulo wsl pasan. Co-Authored-By: Claude Code <noreply@anthropic.com> * feat(wsl): resolver sesiones de los proveedores por entorno (#91) Cablea el SessionResolver en la lectura de uso: en host se mantiene el comportamiento de siempre (disco local, incluido el respaldo del Keychain de macOS); en WSL la sesión de cada CLI (Claude, Codex, Gemini, Grok) se resuelve y se lee DENTRO de la distro, con el home de su default user resuelto por el adaptador (F7). El fichero de sesión por proveedor tiene una sola fuente (credentials_file_name) para que host y WSL compongan la misma ruta relativa. Si el entorno configurado no se puede resolver (WSL sin instalar, distro ausente), el orbe muestra un error explícito de WSL: nunca se cae a una cuenta de otro entorno. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): lecturas del entorno WSL en hilos propios + sello de la distro (#91) Revisión PR #220 (HIGH): el adaptador es síncrono y las lecturas de sesión esperan a wsl.exe (hasta 20 s cada una); hacerlas en el executor async congelaba el widget con varias cuentas. El resolvedor pasa a `Arc<dyn WslAdapter>` y expone `read_async` (spawn_blocking); resolve_home también va en hilo propio. El crate ya usaba este patrón (claude.rs, antigravity.rs). El sello de sesiones (session_stamp) también observa el entorno: en WSL se lee el contenido de los ficheros dentro de la distro (misma fuente que los fetch: credentials_path > home del entorno) y se suma su huella, de modo que un login dentro de la distro refresca el widget aunque el host no tenga mtime accesible. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(wsl): espacio en el doc de Arc del SessionResolver (review 2º pase) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): método del trait en scope y Result en el sello (CI real) Dos errores que solo aparecieron al compilar el crate completo en CI (el harness aislado no incluye lib.rs, que necesita Tauri): - E0599: `resolve_home` es un método del trait WslAdapter, no inherente; con el adaptador en `Arc` el trait tenía que estar en scope para el auto-deref. - E0277: al pasar `session_stamp` a async, Tauri exige que los commands async con `State` devuelvan Result (el futuro vive más que el borrow). Co-Authored-By: Claude Code <noreply@anthropic.com> * style(wsl): rustfmt en el import del trait en lib.rs Co-Authored-By: Claude Code <noreply@anthropic.com> * style: lints de clippy del alcance del milestone - wsl/real.rs y wsl/process.rs: doc comments sin línea vacía / sin indentación de lista (empty_line_after_doc_comments, doc_lazy_continuation). - config.rs: el test de session_env inicializa el struct en vez de reasignar campos (field_reassign_with_default). El resto de lints que reporta clippy son preexistentes de main y el CI del proyecto no ejecuta clippy. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): rutas POSIX en el resolvedor y caché del home por distro (#91) Revisión del mantenedor (2026-11-02) aplicada a la PR de #91: - `SessionResolver::default_path` componía con `Path::join`, que en Windows introduce `\` en una ruta que viaja literal al `cat` de la distro (Linux, donde `\` no separa nada). Ahora usa `join_guest_path` (semántica POSIX) con test del argumento exacto en cualquier plataforma. - `session_stamp` se consulta cada 15 s y cada consulta resolvía el home de la distro con dos procesos wsl.exe (`resolve_home`), manteniendo la VM de WSL potencialmente siempre activa. Nuevo `WslHomeCache` en AppState: el home se resuelve una vez por distro y se invalida solo al cambiar la distro configurada. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras caché y POSIX del resolvedor Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): ligar la comprobación de identidad al TERM y al KILL (#90) Bloqueo de la revisión del mantenedor (2026-11-03): la identidad se comprobaba antes del TERM, pero durante la espera solo se consultaba `kill -0` y el KILL final se enviaba sin volver a comprobar. Si el proceso original moría y su PID se reutilizaba entre sondas, el nuevo dueño recibía el KILL. La comprobación y el envío van ahora en UNA sola invocación (`SCRIPT_SIGNAL_IF_IDENTITY`: `tr … | grep -qxF … && kill -<señal> <pid>`), también para la escalada: no hay proceso de verificación separado que deje ventana entre verificar y señalizar, y si el PID fue reutilizado el grep falla y no se envía nada (fail-closed). Regresión determinista añadida: identidad correcta → TERM → el original muere y el PID se reutiliza entre sondas (todas las `kill -0` ven vivo) → la escalada re-verifica y el proceso nuevo NO recibe KILL. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras el KILL ligado a identidad Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): rutas POSIX en cuentas adicionales y validación de la explícita (#91) Revisión del mantenedor (2026-11-04), dos defectos de rutas solo reproducibles en Windows: 1. Cuentas adicionales ilegibles en WSL. La ruta se componía con `PathBuf::join`, que en Windows inserta `\` en el interior, y `join_guest_path` solo normalizaba la frontera: el guest recibía `/home/u/.quorb\accounts\…` y la cuenta quedaba «Sin sesión» sin error visible. Ahora `account_session_path` compone con POSIX en todo el recorrido; un `config_dir` absoluto del guest se usa tal cual (sin re-enraizar) y lo que no sea POSIX (separadores de Windows o rutas relativas) se rechaza con un error de configuración accionable, que en `fetch_account` se convierte en el report de la cuenta sin tocar la red. Regresión del WslCall exacto en `wsl_session_stamp`. 2. La ruta explícita no se validaba como ruta del guest. `resolve` devolvía `PathBuf::from(explicit)` tal cual: la UNC que sugiere Ajustes (`\\wsl.localhost\…`) viajaba literal al `cat`, fallaba como «no existe» y se mostraba «Sin sesión» ocultando un error de configuración. `resolve` devuelve ahora `Result` y en WSL valida la ruta (POSIX absoluta del guest); los proveedores mapean `Invalid` a un error explícito y el sello omite la ruta inválida. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras POSIX de cuentas y explícita (#91) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): rutas del guest con separador POSIX en todo el recorrido (#91) El CI de Windows confirmó el defecto de la revisión (2026-11-04): el `Path::join` interno inserta `\` también entre componentes (`.quorb\.credentials.json`), no solo en la frontera. Los proveedores componían sus rutas por defecto con `Path::new(".claude").join(...)`. Nuevo `guest_relative` (una sola fuente): compone la relativa del guest con `/` en cualquier plataforma, sin `Path::join`. Se usa en los cuatro proveedores, en la composición de cuentas adicionales (`fetch_account` y `wsl_session_stamp`) y en los tests. Regresión que fija el string POSIX exacto y el `WslCall` de la cuenta adicional. Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras guest_relative (#91) Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): proveedores sin CLI y layout por defecto en WSL (#91) Review rust (2026-11-04): - HIGH: `account_session_path` cortaba también las cuentas de proveedores SIN login por CLI (openrouter, zai, nan, copilot…) en modo WSL, porque el lookup del fichero se evaluaba antes del match por proveedor. Ahora los proveedores sin sesión CLI devuelven `Ok(None)` y su fetch normal sigue usando su token. - MEDIUM (documentado): sin `config_dir`, dos cuentas del mismo proveedor comparten `~/.quorb/<fichero>` (igual que en host); el aislamiento real lo da el config_dir de Ajustes. Test que fija el layout y el choque. - Menores: `trim_end_matches('/')` en config_dir; `guest_relative` solo en tests (sin warning en el build no-test). Co-Authored-By: Claude Code <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras retirar el mensaje de "sin login CLI" Co-Authored-By: Claude Code <noreply@anthropic.com> * fix(wsl): layout host para cuentas por defecto y fuentes únicas (#91) Revisión del mantenedor (2026-11-06): - BLOQUEANTE resuelto: la carpeta por defecto de las cuentas adicionales en WSL es ahora la MISMA que en host — home_dir del canal + `accounts/<provider>-<suffix>` + fichero, con POSIX del guest. Dos cuentas del mismo proveedor sin config_dir ya no comparten `~/.quorb/<fichero>` (el aislamiento que declara wsl/session.rs) y los canales con otro home_dir no divergen. El test que fijaba el comportamiento compartido se invierte (`default_extra_accounts_are_ isolated_without_config_dir`). - Menor 1: la regla «absoluta del guest y sin \» vive en UNA sola fuente (`wsl::guest_path_problem`), usada por `SessionResolver::resolve`, `wsl::session_path` y `account_session_path`. - Menor 2: las rutas de los proveedores principales salen de una única fuente (`provider_session_relative`) usada por los cuatro fetch y por `wsl_session_stamp` (ya no se repiten a mano). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras layout host y fuentes únicas (#91) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * docs(i18n): regenerar el inventario tras integrar main (#239) --------- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: egdev6 <egdev6@gmail.com>
Adaptador WSL (Fase A+B) + lab W3 de evidencia — issue #90
Trae a
mainel adaptador WSL de Quorb con su contrato inyectable y el lab que lo acredita con WSL real.Qué incluye
src-tauri/src/wsl/— solo depende decli_process+ std (sin deps nuevas en Cargo.toml):WslAdapter(mod.rs),MockWslAdapterdeterminista, errores tipados (errors.rs).RealWslAdaptercontrawsl.exe(real.rs), lanzador con cancelación acotada nivel 1 (process.rs), pidfile endurecido.lib.rs: exponepub mod wsl.Lab de evidencia W3 (no es código de producto):
src-tauri/examples/wsl-wslenv-probe.rs: mide WSLENV por el lanzamiento REAL del adaptador (run_command→env_with→ ProcessRunner conenv_clear().envs()), con absent ≠ empty..github/workflows/w3-evidence.yml+scripts/wsl-f14-probe.rs: Gap 1 (pidfile, kill acotado, testigo), Gap 1b (timeout del host), Gap 2 (F14 doble frontera por la vía real), Gap 3 (WSLENV allowlist × ramas + no-duplicar).Hallazgo del lab
Al ejercitar el adaptador real (como pidió la revisión), el lab destapó que
shell_strategyusabaexec "$@"concommand -v bash— perocommandes un builtin, no un binario, así que fallaba con 127 en ambas distros. Corregido conSCRIPT_COMMAND_V(command -v "$1"), y el exit code de la sonda se trata como respuesta (no error): sin bash →ShPosix.Evidencia
Run del lab sobre este head (
6efc038): 41050473110 → ✅ success,VERDICT GLOBAL: PASS— GAP1 PASS, GAP1B PASS, GAP2 PASS (24/24), GAP3 PASS (26/26, 13 por distro). Artefactow3-evidence.🤖 Generated with Claude Code