Skip to content

feat(linux): adaptar sesiones, secretos y login CLI nativos #106

Open

Description

@egdev6

Objetivo y motivo

Que Quorb en Linux lea sesiones y almacene sus propios secretos de forma segura y abra los logins oficiales en el mismo entorno Linux, sin confundirlo con WSL.

Alcance

  • Resolver rutas Linux y sesiones locales de los proveedores presentes, con errores útiles para archivo ausente, formato inválido y sesión caducada; no prometer sesión local de todos los proveedores.
  • Verificar Secret Service/keyring en la sesión de escritorio acordada, incluidos almacenamiento, recuperación, borrado y migración del servicio legado sin revelar valores.
  • Abrir login de Claude, Codex y Gemini mediante terminal Linux con comandos y argumentos seguros, rutas con espacios/no ASCII y errores accionables; no depender ciegamente de x-terminal-emulator si no está disponible.
  • Excluye leer credenciales WSL desde Linux, copiar tokens, instalar CLIs automáticamente, modificar cuentas ajenas y cambiar el contrato Windows/macOS.

Dependencias

#105 baseline Linux integrado y matriz/contrato aprobados. WSL #90–#93 es independiente; coordinar superficies compartidas, sin hacer depender Linux de WSL.

Criterios de aceptación

  • Rutas y precedencia explícita entre configuración manual y defaults Linux, con pruebas de ausente, inválido, espacios/no ASCII y cuentas pertinentes.
  • Secret Service/keyring probado en escritorio real: guardar/leer/borrar y ausencia/denegación del servicio se informan sin filtrar secretos; no degradar a texto plano.
  • Login CLI y diagnóstico seguros y verificables; ausencia de terminal/CLI o cancelación producen resultado accionable y no se ejecuta una cadena arbitraria por interpolación.
  • Proveedores soportados comprobados en Linux real; los no comprobados o dependientes de sesiones no disponibles quedan identificados, sin prometer paridad global.
  • Suites focales y regresión Windows pertinentes, sin reescribir el flujo WSL.

Orientación

src-tauri/src/providers/, src-tauri/src/secrets.rs, src-tauri/src/login.rs, src-tauri/src/config.rs, src/Settings.tsx. Mantener lectura sin escritura en sesiones oficiales; no registrar secretos en logs/tests.

Comprobaciones y ODD

Observar comportamiento y rutas reales de la matriz #105 → decidir contrato de credenciales/terminal y fallos → desarrollar adaptadores con tests RED/GREEN y controles negativos → verificar CLI y Secret Service en Linux real, npm test, tipos/build y cargo test según superficie, más regresión Windows. Salida: evidencia funcional y límites por proveedor, no solo compilación.

Coordinación y entrega

Planificada y bloqueada por #105; puede desarrollarse en paralelo con la unidad de escritorio después de esa integración. Sin assignee inicial. Revalidar responsables, PR y dependencias antes de iniciar; coordinar textos con #66 sin duplicar i18n. Tests y docs junto al código; PR/merge/release requieren autorización separada.

Activity

  1. added this to the Linux portability milestone on Oct 13, 2026
  2. added
    oddTrabajo coordinado mediante planificación ODD aprobada
    on Oct 13, 2026
  3. sosan commented on Oct 22, 2026

    @sosan
    Contributor

    W3 — Verificación en Linux real ejecutada. Registra el estado de los criterios con evidencia del runner, no solo de compilación.

    Runs (workflow linux-ci.yml, ref=feat-linux-l1-106-retake, Ubuntu 22.04):

    • 39698848192 (with_bundle=tests): npm test (628 vitest + 221 node) · npm run build · cargo test --locked (233 passed, 1 ignored) · smoke de Secret Service: 1 passed.
    • 39800266204 (with_bundle=bundle): lo anterior + build de .deb/.AppImage + arranque verificado de ambos en Ubuntu 22.04 limpia (60 s con ventana creada).

    Criterio 2 — Secret Service en sesión real (el que faltaba): el smoke linux_secret_service_round_trip (marcado #[ignore], no corre en CI por diseño) se ejecuta explícitamente en el lab con dbus-run-session + gnome-keyring-daemon --unlock. Guarda → lee → borra un valor de prueba vía el backend linux-native del crate keyring (libsecret/D-Bus), por la misma vía entry()/scoped() que la app. Resultado: OK, sin degradar a texto plano.

    Bug encontrado y corregido en el gate de bundles (relevante para la confianza en la evidencia): los scripts de verify buscaban quorb_*.deb/quorb_*.AppImage en minúscula, pero Tauri produce Quorb_0.7.0_amd64.*; ambos checks se saltaban y el orquestador imprimía «✔ Build-verify OK» sin arrancar nada. Corregido con búsqueda case-insensitive (find -iname) + exit 1 si no hay bundles. El run 39800266204 ya arranca los binarios de verdad (.deb instalado → /usr/bin/quorb vivo; .AppImage vivo vía FUSE).

    Límites de esta evidencia (lo que sigue sin comprobarse automáticamente):

    • Sesiones reales de CLI: sin credenciales de Claude/Codex/Gemini en un runner anónimo, los tests validan parseo y precedencia de rutas, no una sesión viva de cada proveedor.
    • Escritorio real: Xvfb + D-Bus session, no un compositor de escritorio (queda para la matriz física Wayland, ya cubierta aparte).
    • Distribución: solo ubuntu-22.04.

    Evidencia completa (outputs, pasos, límites): docs/linux-l1-evidence/README.md en la rama feat-linux-l1-106-retake (commit 72b18b8).

  4. egdev6 commented on Oct 22, 2026

    @egdev6
    OwnerAuthor

    @sosan he verificado el comentario W3 contra GitHub y el código. La evidencia es real y se sostiene, con un matiz importante sobre dónde viven los runs.

    ✅ Verificado

    Los runs existen y pasan — pero en el fork sosan/quorb, no en egdev6/quorb (ahí los IDs dan 404):

    Run Ref Head Resultado
    39698848192 feat-linux-l1-106-retake 5ca36e4 ✅ success (22 oct 00:17)
    39800266204 feat-linux-l1-106-retake d7a0f8c ✅ success (22 oct 14:57)
    • Smoke Secret Service: confirmado en el log del run 1 — test secrets::tests::linux_secret_service_round_trip ... ok (1 passed, 233 filtered out), con dbus-run-session + gnome-keyring-daemon --unlock y cargo test --locked -- --ignored. Coincide con el criterio 2.
    • Arranque de bundles: confirmado en el log del run 2 — ✔ .deb OK: quorb siguió vivo a los 60s bajo X virtual (ventana creada), ✔ .AppImage OK: montó vía FUSE y siguió vivo 60s y ✔ Build-verify OK: los bundles presentes en /artifacts han arrancado.
    • Bug del gate de bundles (d7a0f8c): el fix es correcto y relevante — los scripts buscaban quorb_*.deb (minúscula) pero Tauri produce Quorb_0.7.0_amd64.*; ambos checks se saltaban y el gate era un verde vacío. Ahora find -iname + exit 1 si no hay ningún bundle.
    • Documentación: docs/linux-l1-evidence/README.md (72b18b8) existe, es detallada y honesta con los límites (sin sesiones CLI reales, sin escritorio real, solo ubuntu-22.04).
    • Conteo: cargo test --locked = 233 passed + 1 ignored, coincide con lo declarado.

    🟡 Observaciones

    1. Trazabilidad: el comentario no dice que los runs están en el fork (el README sí, en su comando de reproducción con --repo sosan/quorb). Quien audite egdev6/quorb obtendrá 404. Conviene enlazar los runs con URL completa del fork. Además la rama feat-linux-l1-106-retake ya no existe en el upstream y no hay PR abierto — la evidencia es difícil de reproducir para terceros.
    2. Cobertura de AC2 parcial: el smoke valida guardar/leer/borrar, pero el criterio 2 también pide «migración del servicio legado sin revelar valores» — migrate_legacy() en secrets.rs existe pero no se ejercita en la evidencia.
    3. Gap menor en verify.sh: si falta solo uno de los dos tipos de bundle, ese check sigue haciendo exit 0 con «AVISO» (el gate solo falla si no hay ninguno). El mensaje nuevo lo dice honestamente («los bundles presentes»), pero se podría endurecer por tipo.
    4. Password hardcodeada (quorb-ci-testpass) en el YAML del smoke: aceptable (keyring efímero del runner, datos de prueba), pero el snippet del README muestra $PASS mientras el workflow la tiene fija. Trivial.

    Veredicto

    Comentario fiable: todos los hechos declarados se confirman en logs y código. Sugerencias: (a) enlazar los runs con URL del fork explícita aquí, (b) abrir el PR upstream pronto para que rama y evidencia sean alcanzables, (c) registrar la migración legacy como lo pendiente del criterio 2.

  5. egdev6 commented on Oct 22, 2026

    @egdev6
    OwnerAuthor

    @egdev6 Pendiente registrado del criterio 2 (Secret Service), tras revisar la evidencia W3:

    La migración del servicio legado sigue sin ejercitarse. El smoke linux_secret_service_round_trip valida guardar → leer → borrar vía entry()/scoped(), pero migrate_legacy() (src-tauri/src/secrets.rs) — la lectura de la clave del servicio legado cuando el destino actual no la tiene — no aparece en la evidencia. El AC2 pide «almacenamiento, recuperación, borrado y migración del servicio legado sin revelar valores».

    Propuesta de cierre: extender el smoke (o añadir un segundo smoke #[ignore]) que pueble una entrada en el espacio de nombres legado y verifique que entry() la recupera por la vía de migración, sin imprimir el valor. Criterio de aceptación: la clave legada se lee una vez, se reescribe en el destino actual y desaparece de futuras lecturas directas al legado.

  6. sosan commented on Oct 22, 2026

    @sosan
    Contributor

    Respuesta a la revisión (4 observaciones) + nueva evidencia de fixtures

    1 · Trazabilidad — runs con URL del fork (sosan/quorb, donde viven los workflow_dispatch):

    La rama feat-linux-l1-106-retake está empujada al fork; abro el PR upstream en paralelo para que rama y evidencia sean alcanzables desde egdev6/quorb (lo enlazo aquí en cuanto esté abierto).

    2 · AC2 — migrate_legacy() pendiente, registrado: la sección 4 de docs/linux-l1-evidence/README.md lo deja escrito como pendiente: poblar LEGACY_SERVICE, llamar get con la clave, verificar que migra al servicio actual y que el legacy desaparece. Sin ejercitar en la evidencia (requiere Secret Service real con clave legacy), con la propuesta de validarlo en el mismo step del smoke o en máquina real.

    3 · Gate por tipo, endurecido: cada verify-deb.sh / verify-appimage.sh ahora hace exit 1 si falta su propio tipo de bundle (antes exit 0 + «AVISO»). El orquestador solo declara OK si ambos están presentes y arrancan. Commit 2f33507.

    4 · Password alineada: el README documenta KEYRING_PASS y el snippet de reproducción usa $KEYRING_PASS; el workflow sigue con quorb-ci-testpass fija (keyring efímero del runner, datos de prueba).

    Nueva evidencia — run 39837767677, log leído directamente:

    • 3 fixtures de sesión real por proveedor (Claude/Codex/Gemini) + tests deterministas con reloj inyectado: parses_real_session_fixture (×2) y parses_real_session_fixture_returns_valid_token_without_http → todos ok (cargo test --locked: 249 passed, 0 failed, 1 ignored = el smoke, ejecutado aparte y también ok).
    • El fixture de Claude replica el layout documentado de Claude Code v2.1.x (accessToken/refreshToken/expiresAt/scopes + subscriptionType). La evidencia ahora documenta dónde vive la sesión por plataforma: macOS → Keychain (Claude Code-credentials, ya cubierto por keychain_fallback) vs Linux/WSL/Windows → JSON.
    • Corregido el warning del refactor de Gemini (unused variable en token_needs_refresh, commit 99ae704).

    Límites que se mantienen (sección 6 del README): sin llamada autenticada a ningún proveedor (los fixtures no tienen credenciales), sin escritorio real, solo ubuntu-22.04, y Quorb no lee CLAUDE_CODE_OAUTH_TOKEN (solo archivo y Keychain).

  7. sosan commented on Oct 22, 2026

    @sosan
    Contributor

    PR upstream abierto: #176 (feat-linux-l1-106-retake → egdev6/quorb:main). Rama y evidencia ya son alcanzables desde egdev6/quorb; incluí también el merge con el main actual (c2f7a12) para que el diff venga limpio.

  8. sosan commented on Oct 28, 2026

    @sosan
    Contributor

    @egdev6 el punto 2 de tu revisión (migración del servicio legado, criterio AC2) queda ejercitado con evidencia, no solo registrado.

    AC2 — migrate_legacy() ejercitado en Secret Service real

    Añadido un segundo smoke #[ignore] (linux_legacy_migrate_once_then_legacy_is_gone, en src-tauri/src/secrets.rs) hermano del round trip existente, y el paso "Secret Service smoke" de linux-ci.yml ahora ejecuta ambos bajo el mismo dbus-run-session + gnome-keyring efímero.

    Run: 40651008234 → ✅ success, SHA 0c75fab (rama feat-linux-l1-106-retake), log directo:

    • linux_secret_service_round_trip ... ok
    • linux_legacy_migrate_once_then_legacy_is_gone ... ok

    Qué verifica el smoke de migración (tu criterio exacto, sin imprimir valores):

    1. Puebla el servicio legado (LEGACY_SERVICE = es.egdev.ai-usage) directamente, sin pasar por set/get (que migrarían o escribirían en el destino actual) — escenario de un secreto que quedó en el servicio anterior al cambiar el identificador.
    2. get() con el destino actual vacío lo recupera por la vía de migración (se lee una vez).
    3. El valor queda reescrito en el destino actual (es.egdev.quorb), comprobado leyendo el entry actual sin volver a migrar.
    4. El legado desaparece de futuras lecturas directas (NoEntry tras la migración).
    5. CleanupGuard limpia ambos servicios al terminar.

    Detalle de diseño que el fallo del primer intento reveló (corregido en 0c75fab): get() no migra claves con sufijo de cuenta — if key.contains('@') … return current;. La migración legacy solo aplica a claves base. El smoke usa nan_session (clave base de la lista cerrada ALL), con asserts que exigen clave base + pertenencia a ALL.

    Con esto, los 4 puntos de tu última revisión quedan con evidencia: trazabilidad (runs/PR #176), AC2 (smoke de migración, ahora ejecutado), gate por tipo endurecido (2f33507) y password documentada. Quedo pendiente de tu contraste.

  9. egdev6 commented on Oct 28, 2026

    @egdev6
    OwnerAuthor

    @sosan gracias, he contrastado el código y los logs del run 40651008234, con checkout en 0c75fab. Confirmo ambos smokes ejecutados y correctos: la migración legacy queda ejercitada en Secret Service real del runner, con escritura directa en legacy, recuperación mediante get(), lectura directa del destino actual y NoEntry en legacy. Usar una clave base es correcto porque las claves con @ no migran.

    Antes de integrar, necesitamos proteger la ejecución manual del nuevo smoke: utiliza la clave real nan_session y llama a get() antes de crear el CleanupGuard. En una sesión personal con una credencial legacy, esa llamada puede migrarla y después fallar el assert, habiendo modificado ya el llavero. Además, None no distingue ausencia de errores de lectura. Por favor, confina el smoke a un llavero desechable con aislamiento verificable del bus y del almacenamiento, y sustituye la indicación de ejecutar los tests ignorados directamente en el escritorio por un procedimiento aislado. Mover el guard no basta, porque su limpieza también podría borrar datos reales.

    Acepto esta evidencia concreta de migración en CI, sin extenderla al cierre global de #106. Para entregar el cambio, hace falta una nueva PR sobre main (el smoke y su paso de workflow no entraron en #176, ya fusionada) y actualizar el README de evidencia, que todavía marca la migración como pendiente, enlazando este run y documentando la ejecución segura.

  10. sosan commented on Oct 28, 2026

    @sosan
    Contributor

    @egdev6 aplicados los tres puntos de tu revisión sobre el smoke de migración. La entrega irá en una PR nueva sobre main (el smoke no entró en #176), que abro ahora y enlazo aquí.

    Arreglos respondiendo a tu revisión (2026-10-28)

    1. Confinamiento a llavero desechable con aislamiento verificable — el smoke linux_legacy_migrate_once_then_legacy_is_gone ahora:

    • Exige QUORB_SECRETS_SMOKE_ISOLATED=1 y falla con assert! antes de tocar nada (ni siquiera crea el Entry) si no está definida. La variable solo se exporta dentro del dbus-run-session + gnome-keyring efímero en el paso "Secret Service smoke" de linux-ci.yml (bus aislado + almacenamiento efímero del runner). Fuera de ahí, el smoke se niega a ejecutarse: nunca en un escritorio con llavero real.
    • CleanupGuard creado antes de cualquier lectura y su Drop re-verifica la variable de aislamiento antes de borrar (defensa en profundidad tras el rust-review): si la precondición falló en un entorno con llavero real (var filtrada), la limpieza no borra nada.
    • La precondición del destino actual vacío distingue NoEntry de error de lectura (vía entry().get_password(), no get()) — None ya no puede confundir ausencia con fallo.
    • Instrucción de escritorio sustituida: los doc comments de ambos smokes (round trip y migración) ya no dicen "ejecutar en sesión de escritorio"; la única vía soportada es el paso aislado de linux-ci.yml, documentado en el README de evidencia.

    2. README de evidencia actualizado — la migración pasa de "pendiente" a "ejercitada" (run 40651008234), con la ejecución segura descrita y descartada la ejecución manual en escritorio.

    3. Revisión adicional (rust-reviewer) — 1 MEDIUM + 2 LOW, todos corregidos:

    • MEDIUM: re-verificación de la var en el Drop del guard (arriba).
    • LOW: el round trip ahora exige el mismo aislamiento (coherencia doc/código).
    • LOW: doc ajustado — el valor no se loguea en el camino feliz (solo en un assert_eq! si algo falla, y es constante de prueba no secreta).

    Aceptando tu criterio de no extender esto al cierre global de #106: la PR nueva aporta solo el smoke + workflow + README.

  11. sosan commented on Oct 28, 2026

    @sosan
    Contributor

    PR abierta con los arreglos de la revisión: #203 — smoke de migración legacy aislado + README de evidencia, sobre main actual (0.8.0), diff confinado a 3 archivos.

  12. added
    status:approvedApproved for implementation by the repository owner
    on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    oddTrabajo coordinado mediante planificación ODD aprobadastatus:approvedApproved for implementation by the repository ownertype:featureNew functionality

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions