Skip to content

feat(wsl): adaptador WSL (Fase A+B) + lab W3 de evidencia — issue #90 - #220

Merged
egdev6 merged 43 commits into
egdev6:mainfrom
sosan:feat/wsl-adapter
Nov 3, 2026
Merged

egdev6 merged 43 commits into
egdev6:mainfrom
sosan:feat/wsl-adapter

Conversation

@sosan

@sosan sosan commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Adaptador WSL (Fase A+B) + lab W3 de evidencia — issue #90

Trae a main el 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 de cli_process + std (sin deps nuevas en Cargo.toml):

  • Fase A: contrato WslAdapter (mod.rs), MockWslAdapter determinista, errores tipados (errors.rs).
  • Fase B: RealWslAdapter contra wsl.exe (real.rs), lanzador con cancelación acotada nivel 1 (process.rs), pidfile endurecido.
  • lib.rs: expone pub 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 con env_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_strategy usaba exec "$@" con command -v bash — pero command es un builtin, no un binario, así que fallaba con 127 en ambas distros. Corregido con SCRIPT_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). Artefacto w3-evidence.

🤖 Generated with Claude Code

sosan and others added 30 commits October 24, 2026 21:59
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>
@egdev6

egdev6 commented Oct 30, 2026

Copy link
Copy Markdown
Owner

@sosan gracias. He revisado el head 6612082 y lo he contrastado en local: cargo test wsl → 61 passed / 0 failed, y node scripts/i18n-inventory.mjs --check → falla. No hay ningún run de CI sobre 6612082 (0 runs para ese SHA); los últimos de PR (14:24, sobre 6efc038) tienen CI, Linux y macOS en rojo, Security y Code safety en verde.

Resuelto en 6612082 ✅

  1. E0382 — shell_strategy_uses_command_v_script_not_exec pasa runner.clone() y conserva la aserción de lanzamiento; FakeRunner ya deriva Clone por Arc<Mutex>. Compila y pasa en Linux.
  2. let mut args = args; redundante en list_distributions eliminado.
  3. Comentario de parse_env en el probe documentado (limitación del salto de línea).

Sigue abierto ❌

  1. shell_strategy sigue confundiendo error WSL con ausencia de Bash. En real.rs:309–324 no cambió nada: control_raw devuelve Ok con el exit sin clasificar y el else cae a ShPosix. Si la distro desaparece entre la enumeración y la sonda, WSL_E_DISTRO_NOT_FOUND sigue convirtiéndose en una estrategia válida en vez de DistroNotFound. errors::classify ya detecta esos tokens y no se invoca en este path. Conserva el fallback para el fallo normal de command -v (el test shell_strategy_treats_nonzero_command_v_as_posix con stderr vacío es correcto), pero propaga los errores WSL reconocidos y añade la regresión que distinga ambos casos.

  2. Escalada después de haber observado la muerte. En process.rs:471–490 tampoco cambió: el bucle de TERM_PROBES puede salir por !alive (o tratar un Err como muerto) y la «segunda comprobación» de la línea 484 es incondicional. Si el PID se reutilizó en la distro, kill -0 devuelve 0 y se envía KILL a un proceso ajeno. Hace falta: (a) no escalar si el bucle ya observó la desaparición, (b) proteger la identidad de la instancia antes de enviar señales, (c) un test determinista donde el PID desaparezca y reaparezca con otra identidad y se demuestre que no recibe KILL. kill_escalates_to_kill_only_when_term_is_ignored no cubre ese caso: el PID nunca desaparece y reaparece.

  3. session_path sigue con semántica del host. En mod.rs:179–204 sin cambios: Path::is_absolute() y PathBuf::join usan reglas Windows cuando Quorb corre en Windows, así que /home/u/.codex/auth.json se rechaza como no absoluta y /etc/passwd no se identifica como absoluta al validar la ruta relativa del proveedor. Hay que corregirlo y cubrirlo con tests.

Hallazgo nuevo: el inventario i18n sigue desactualizado ⚠️

En 6612082 el inventario se regeneró antes de la última edición de real.rs (mismo commit). El header commiteado declara src-tauri/src/ = c85a6325…, pero el árbol real del head regenera 47e95538… (los textos coinciden; solo el hash). El gate de Windows en ci.yml:48 seguiría en rojo.

Además la rama va 2 commits por detrás de main (faltan a0bc5ed #207 y f3897bc #219), y esos commits tocan 10 ficheros de src/. Como CI corre sobre el merge ref, el orden correcto es merge → regenerar inventario → push; regenerar sin mergear no alcanza.

Pendientes menores

  • La evidencia W3 sigue enlazada a 6efc038; no hay run sobre 6612082 (solo cambió test/docs, pero el criterio pedía el head final).
  • El hallazgo descartado de inyección (exec "$@") lo doy por bien descartado: los posicionales entrecomillados no re-evalúan $(...) y read_file usa run_scoped con cancelación acotada a propósito.

Veredicto: lo táctil de este commit está resuelto y los tests Rust pasan en local, pero los dos hallazgos principales de mi revisión anterior siguen intactos, más session_path, y el gate i18n sigue en rojo. Con estos cambios, CI verde sobre el head final y W3 re-enlazada, la PR queda lista para la revisión de integración. Revisión estática más verificación local; no he ejecutado Windows ni he corrido la evidencia W3.

sosan and others added 3 commits October 30, 2026 19:38
…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>
@sosan

sosan commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

@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 f48a6f8)

  1. shell_strategy ya no confunde error WSL con ausencia de Bash. Tras control_raw, se clasifica la salida con errors::classify: si 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 (DistroNotFound) en vez de caer a ShPosix. El fallback a ShPosix queda solo para el command -v normal sin bash (salida vacía, sin token). Regresión: shell_strategy_propagates_wsl_distro_loss_not_sh_posix con el stderr de WSL_E_DISTRO_NOT_FOUND.

  2. kill_remote_process ya no escala tras observar la muerte. Si una sonda kill -0 ve el PID muerto, se rompe el bucle y se descarta la segunda comprobación/KILL (un kill -0 posterior podría devolver 0 porque el PID fue reutilizado por otro proceso de la distro). La identidad de la instancia se protege observando la desaparición. Test determinista case_disappears_then_reappears: la primera sonda ve el PID muerto y las siguientes lo ven vivo (reutilizado) → no recibe KILL.

  3. session_path usa semántica del guest (POSIX). Path::is_absolute() en Windows decía false para /home/u/... y true para C:\..., justo al revés del guest. Nuevo helper is_guest_absolute (empieza por /) y dos tests: una ruta Windows explícita se rechaza en modo WSL, y /etc/passwd como relativa del proveedor se rechaza aunque Windows no la considere "absoluta".

Inventario i18n

El gate compara el hash del árbol src-tauri/src/, así que los cambios de código dejaban el inventario desactualizado aunque los textos no variaran. Regenerado tras el merge de main (cb075d1) y verificado con node scripts/i18n-inventory.mjs --check (exit 0).

CI y W3 sobre el head final cb075d1

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.

@egdev6

egdev6 commented Oct 31, 2026

Copy link
Copy Markdown
Owner

@sosan, revisé el head cb075d19774ebec08bf6706cb19c6097b3c56088 y contrasté CI y los logs de W3. La clasificación de errores en shell_strategy y su regresión de pérdida de distro están resueltas. También confirmé los cinco checks verdes sobre ese head y el run W3 41123308916 sobre el mismo SHA, con VERDICT GLOBAL: PASS. Esa evidencia ya está cumplida.

Quedan dos puntos antes de integrar:

  1. Identidad del proceso, no solo desaparición observada. En src-tauri/src/wsl/process.rs:463–501, el nuevo guard evita KILL después de observar la muerte, pero el pidfile sigue guardando únicamente el número de PID. Si el proceso muere y el número se reutiliza entre sondas, todas pueden devolver «vivo» y se acaba señalizando otro proceso. TERM también se envía antes de verificar identidad. Observar una desaparición evita un recorrido concreto, pero no protege la identidad de la instancia. Hace falta un mecanismo que la compruebe antes de señalizar y una regresión donde la identidad cambie sin que ninguna sonda intermedia observe el PID ausente. El test nuevo cubre correctamente la desaparición observada, no ese caso. Es un hallazgo estático; no reproduje una reutilización de PID en runtime.

  2. Composición de rutas todavía dependiente del host. src-tauri/src/wsl/mod.rs:217 conserva mode_home.join(relative). La detección de rutas absolutas ya usa reglas POSIX, pero en Windows la composición sigue usando PathBuf del host y puede introducir un separador \, que read_file() (real.rs:270–277) pasa literalmente como argumento al cat del guest. Los tests actuales comparan PathBuf, cuya comparación por componentes puede ocultar la diferencia de separadores. Hace falta componer con semántica POSIX y probar en Windows el string/argumento exacto que llega al guest, por ejemplo /home/quorbuser/.claude/.credentials.json.

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>
@egdev6

egdev6 commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Gracias por seguir avanzando con esto, @sosan. Revisé el head 71b2223 y todavía no está lista para integrar.

Siguen abiertos los dos puntos de mi revisión del 31/10:

  1. Identidad del proceso. src-tauri/src/wsl/process.rs no cambió desde cb075d1: el pidfile sigue guardando solo el número de PID y no se comprueba que sea la misma instancia antes de enviar TERM/KILL. Hace falta esa comprobación y una regresión donde la identidad cambie sin que ninguna sonda intermedia observe el PID ausente.
  2. Rutas con semántica del host. src-tauri/src/wsl/mod.rs:219 mantiene mode_home.join(relative), y el código nuevo repite el mismo patrón: src-tauri/src/wsl/session.rs:132-133 compone la ruta de sesión WSL con self.home.join(relative). En Windows ambos pueden introducir \ en una ruta que se pasa literalmente al guest. Hace falta componer con semántica POSIX y comprobar en Windows el argumento exacto que llega al guest.

El alcance creció sin actualizar la PR. Los commits nuevos añaden SessionResolver y conectan los proveedores (Claude, Codex, Gemini, Grok, configuración y lib.rs), referenciando #91, mientras el título y la descripción siguen siendo «Fase A+B, issue #90». En total son más de 5.000 líneas en 17 archivos, muy por encima de lo que se puede revisar con garantías en una sola PR. Pido separar la conexión de proveedores (#91) en una PR propia, encadenada sobre esta, y dejar aquí solo el adaptador y el lab de #90.

Para la PR de #91, un punto a tener en cuenta: session_stamp se consulta cada 15 s (SESSION_CHECK_MS). En modo WSL, cada consulta resuelve el home lanzando dos procesos wsl.exe, lo que además puede mantener la VM de WSL siempre activa. Conviene cachear el home por distro e invalidarlo solo cuando cambie la configuración.

Además: la rama tiene conflictos con main, así que la CI no se está ejecutando. Recuerda el orden: merge de main, regenerar el inventario i18n y después push.

Esta revisión fue estática sobre el head indicado; no ejecuté el código de la PR. ¡Gracias!

sosan added a commit to sosan/quorb that referenced this pull request Nov 2, 2026
…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>
sosan added a commit to sosan/quorb that referenced this pull request Nov 2, 2026
…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>
sosan added a commit to sosan/quorb that referenced this pull request Nov 2, 2026
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>
sosan added a commit to sosan/quorb that referenced this pull request Nov 2, 2026
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>
@egdev6

egdev6 commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Gracias por seguir avanzando, @sosan. Seguimos revisando el proceso y los cambios: en el head 600033c ya están resueltos los conflictos con main y los cinco checks de CI pasan. Ese avance queda confirmado.

Aun así, el trabajo todavía no está completo para integrar. Siguen pendientes los puntos de la revisión anterior:

  • Identidad del proceso antes de TERM/KILL: comprobar que el PID corresponde a la misma instancia, no solo que existe, y cubrir la reutilización sin una desaparición observada entre sondas.
  • Rutas del guest con semántica POSIX: siguen presentes mode_home.join(relative) y self.home.join(relative). Hace falta corregir la composición y comprobar en Windows el argumento exacto enviado al guest.
  • Separar el alcance de feat: resolver credenciales y sesiones CLI desde WSL #91: la conexión de proveedores debe ir en una PR propia encadenada, dejando aquí el adaptador y el lab de feat: configurar y detectar el entorno WSL en Windows #90.

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!

sosan and others added 3 commits November 2, 2026 20:42
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>
@egdev6

egdev6 commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Gracias por los cambios, @sosan. Revisé el head 232d64a y hay avances importantes: la integración de proveedores de #91 ya no forma parte de esta PR, las rutas se componen con semántica POSIX y los tests comprueban que no llevan separadores Windows. También verifiqué que el inventario coincide con el generador y que los cinco checks están en verde.

Queda un bloqueo en la escalada a KILL. En src-tauri/src/wsl/process.rs:507–520 se comprueba el token de instancia antes de TERM, pero durante la espera solo se consulta kill -0; finalmente, en 532–556, se envía KILL sin volver a comprobar la identidad.

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!

sosan added a commit to sosan/quorb that referenced this pull request Nov 3, 2026
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>
sosan added a commit to sosan/quorb that referenced this pull request Nov 3, 2026
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>
sosan and others added 2 commits November 3, 2026 20:10
…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>
sosan added a commit to sosan/quorb that referenced this pull request Nov 3, 2026
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>
@egdev6

egdev6 commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

¡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 shell_strategy con exec "$@" y command -v bash. Eso es exactamente lo que se espera de una evidencia real y rigurosa.

Todo queda verificado sobre este head 311cc24:

  • La identidad de instancia va ligada a TERM y KILL en la misma invocación, con la regresión del PID reutilizado durante la espera.
  • Las rutas del guest se componen con semántica POSIX y los tests fijan el argumento exacto.
  • Los cinco checks en verde y el inventario coinciden con el generador.
  • W3 ejecutado sobre este mismo SHA (41704092602) con GAP1, GAP1B, GAP2, GAP3 PASS y VERDICT GLOBAL: PASS.

Un par de notas para tener presentes, sin que afecten a la aprobación. Queda una ventana mínima entre grep y kill dentro del mismo shell; cerrarla del todo requeriría pidfd. Y la integración real de los proveedores en #91 traerá el pendiente de cachear el home por distro para no lanzar wsl.exe cada 15 segundos.

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.

@egdev6
egdev6 merged commit f7b614a into egdev6:main Nov 3, 2026
5 checks passed
@egdev6 egdev6 mentioned this pull request Nov 5, 2026
egdev6 added a commit that referenced this pull request Nov 6, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants