Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
53d2988
ci(wsl): registrar el lab W3 en main para poder despacharlo
sosan Oct 24, 2026
e6c3537
fix(w3-lab): usar windows-2022 (windows-latest ya no trae WSL)
sosan Oct 24, 2026
19a9a4c
fix(w3-lab): windows-latest + sonda --version + exit 0 (igual a la rama)
sosan Oct 24, 2026
29e4e4c
fix(w3-lab): sha256 a fichero (PS7 .Content es byte[]) — igual a la rama
sosan Oct 24, 2026
12f578c
fix(w3-lab): veredicto global + evidencia always() (igual a la rama)
sosan Oct 25, 2026
5840755
fix(w3-lab): volcado del import al log (igual a la rama)
sosan Oct 25, 2026
4601676
fix(w3-lab): import con directorio creado (igual a la rama)
sosan Oct 25, 2026
527aa38
fix(w3-lab): Gap 1 con testigo (igual a la rama)
sosan Oct 25, 2026
560468b
fix(w3-lab): senal de fichero + .wslconfig (igual a la rama)
sosan Oct 25, 2026
7c28615
fix(w3-lab): configurar WSL del runner (igual a la rama)
sosan Oct 25, 2026
4b95f66
fix(w3-lab): shift antes del exec (igual a la rama)
sosan Oct 25, 2026
df7ad95
fix(w3-lab): background + EXIT aparte (igual a la rama)
sosan Oct 25, 2026
e4b11e5
fix(w3-lab): Start-Job para el launch (igual a la rama)
sosan Oct 25, 2026
923734f
fix(w3-lab): sleep 1 + diagnostico en vivo (igual a la rama)
sosan Oct 25, 2026
767529b
fix(w3-lab): background dentro de la distro (igual a la rama)
sosan Oct 25, 2026
e0be03d
fix(w3-lab): redireccion en una linea (igual a la rama)
sosan Oct 25, 2026
0ef12fd
feat(w3-lab): Gap 1b (igual a la rama)
sosan Oct 25, 2026
6ea6651
fix(w3-lab): script a fichero en Gap 1b (igual a la rama)
sosan Oct 25, 2026
db11ff8
fix(w3-lab): vmIdleTimeout=-1 (igual a la rama)
sosan Oct 25, 2026
6f77526
fix(w3-lab): veredicto global solo por Gap 2 (igual a la rama)
sosan Oct 25, 2026
c58cd47
fix(w3-lab): Gap 1b continue-on-error (igual a la rama)
sosan Oct 25, 2026
a25dafd
fix(w3-lab): Stop-Process tolerante (igual a la rama)
sosan Oct 25, 2026
bfd1464
fix(w3-lab): caso empty como string (igual a la rama)
sosan Oct 25, 2026
80827c4
feat(w3-lab): probar Ubuntu con systemd (igual a la rama)
sosan Oct 25, 2026
07f7763
feat(w3-lab): Ubuntu por wsl --install (igual a la rama)
sosan Oct 26, 2026
89b9200
fix(w3-lab): desactivar OOBE de Ubuntu (igual a la rama)
sosan Oct 26, 2026
4d81a05
fix(w3-lab): restaurar list-after (igual a la rama)
sosan Oct 26, 2026
5fb40f9
Merge branch 'egdev6:main' into main
sosan Oct 27, 2026
76d2b2a
Merge branch 'egdev6:main' into main
sosan Oct 28, 2026
7d22295
feat(wsl): adaptador WSL (Fase A+B) + lab W3 de evidencia — issue #90
sosan Oct 30, 2026
234fb11
Merge branch 'egdev6:main' into main
sosan Oct 30, 2026
6efc038
Merge branch 'main' into feat/wsl-adapter
sosan Oct 30, 2026
6612082
fix(wsl): corregir borrow de runner en test, retoques de review y i18…
sosan Oct 30, 2026
f34a340
merge: integrar main del upstream (notarización macOS, i18n y fijacio…
sosan Oct 30, 2026
f48a6f8
fix(wsl): aplicar los 3 hallazgos de la revisión PR #220
sosan Oct 30, 2026
cb075d1
docs(i18n): regenerar el inventario tras los cambios de src-tauri/src
sosan Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
991 changes: 991 additions & 0 deletions .github/workflows/w3-evidence.yml

Large diffs are not rendered by default.

56 changes: 53 additions & 3 deletions docs/i18n-inventory.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# i18n inventory (egdev6/quorb#66)

Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `c9ffea3ddacbdf22d2fa5509030ba29ded394734` and `src-tauri/src/` = `71fa2ce6c1ffbdf5a1f3fec0925dfed7a6a505ed`.
Generated by `node scripts/i18n-inventory.mjs` from the source trees `src/` = `c9ffea3ddacbdf22d2fa5509030ba29ded394734` and `src-tauri/src/` = `ad1331acab6b971382816c89ba03582999aa0e79`.
Any commit whose `git rev-parse <commit>:src` and `<commit>:src-tauri/src` match has these exact texts.
Do not edit by hand: regenerate, or verify with `node scripts/i18n-inventory.mjs --check`.

Expand All @@ -19,8 +19,8 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
| Area | Distinct texts | Sites |
|---|---:|---:|
| React (`src/`) | 958 | 1066 |
| Rust (`src-tauri/src/`) | 421 | 502 |
| Total | 1342 | 1568 |
| Rust (`src-tauri/src/`) | 453 | 540 |
| Total | 1373 | 1606 |

## src-tauri/src/channel.rs

Expand Down Expand Up @@ -580,6 +580,56 @@ tuples (e.g. the `"req"` unit in `providers/nan.rs`) are only listed when they a
- Wayland detectado. Si siempre-encima o clic-a-través fallan, activa «Auto-hide» en Ajustes.
- X11 (u otro). Compatibilidad completa con always-on-top y click-through.

## src-tauri/src/wsl/errors.rs

- Converting
- Installing
- Running
- Stopped
- Uninstalling
- has no installed distributions
- invalid command line argument
- is not installed
- no installed distributions
- no such distribution
- not enabled
- not registered
- wsl.exe rechazó los argumentos (Invalid command line argument)

## src-tauri/src/wsl/mock.rs

- la distro configurada no aparece en la enumeración; disponibles: {}

## src-tauri/src/wsl/mod.rs

- credentials_path en modo WSL debe ser una ruta absoluta de la distro (empieza por '/'), se recibió: {p}
- la ruta del proveedor dentro del modo debe ser relativa, se recibió: {rel}

## src-tauri/src/wsl/process.rs

- Path
- Sysnative
- b="$1" && shift &&
- cat "$1"/*/pid
- command -v "$1"
- d=$(mktemp -d "$b/XXXXXX") &&
- echo $$ > "$d/pid" &&
- exec "$@"
- mkdir -m 700 "$b" &&
- rm -rf "$1"
- umask 077 &&
- wsl.exe no se pudo ejecutar (error de E/S)
- wsl.exe terminó con código {}

## src-tauri/src/wsl/real.rs

- Path
- getent passwd sin campo home
- la distro configurada no aparece en la enumeración; disponibles: {}
- wsl.exe devolvió la salida en UTF-16 (WSL_UTF8 no soportado)
- wsl.exe no devolvió el usuario por defecto de la distro
- wsl.exe no soporta --version (build antigua, sin WSL2)

## src/Settings.tsx

- %APPDATA%\\Cursor\\User\\globalStorage\\state.vscdb
Expand Down
90 changes: 90 additions & 0 deletions scripts/wsl-f14-probe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Harness F14 para el lab W3 (issue #90).
//
// Mide la DOBLE FRONTERA con la vía de lanzamiento REAL del adaptador:
// pwsh -> este binario (Rust `Command::args` -> CreateProcessW con quoting
// MSVCRT/CommandLineToArgvW) -> wsl.exe -> sh -c <script CONSTANTE> -> exec.
// El punto que el reviewer reclamó: los argumentos ORIGINALES (espacios,
// comillas, $, backtick, newline, glob, --, ;, C:\...) deben atravesar esa
// frontera byte a byte; el harness pwsh anterior no podía medirlo porque
// `& wsl.exe` de PowerShell no aplica el quoting MSVCRT y wsl.exe re-parsea
// la línea (microsoft/WSL WslClient.cpp, ParseLegacyArguments).
//
// SCRIPT_EXEC_PIDFILE es IDÉNTICO al de src-tauri/src/wsl/process.rs
// (5bb622b): script constante, datos como parámetros posicionales, nada se
// interpola. El pidfile no altera la salida: solo fija el PID de la sesión
// remota y se limpia al final.
//
// Uso: wsl-f14-probe.exe <distro> <value>
// Emite JSON plano: {"exit":N,"stdout_b64":"..","stdout_len":N,"stderr_b64":".."}
// (sin serde; b64 de la salida cruda para transporte seguro del log).
use std::process::{Command, ExitCode, Output};

const SCRIPT_EXEC_PIDFILE: &str = "b=\"$1\" && shift && mkdir -m 700 \"$b\" && d=$(mktemp -d \"$b/XXXXXX\") && umask 077 && echo $$ > \"$d/pid\" && exec \"$@\"";

fn base64(bytes: &[u8]) -> String {
const T: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::new();
for chunk in bytes.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = ((b[0] as u32) << 16) | ((b[1] as u32) << 8) | (b[2] as u32);
out.push(T[((n >> 18) & 63) as usize] as char);
out.push(T[((n >> 12) & 63) as usize] as char);
out.push(if chunk.len() > 1 {
T[((n >> 6) & 63) as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
T[(n & 63) as usize] as char
} else {
'='
});
}
out
}

fn run(distro: &str, value: &str) -> Output {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let base = format!("/tmp/quorb-f14-{}-{nanos}", std::process::id());
// Igual que shell_args(ShPosix) + run_scoped del adaptador: el script va
// como ELEMENTO de argv y el valor como posicional ($1 tras argv0 "quorb"
// y el base). Command::args aplica el quoting MSVCRT de CreateProcessW.
let mut cmd = Command::new("wsl.exe");
cmd.args(["-d", distro, "-e", "sh", "-c", SCRIPT_EXEC_PIDFILE, "quorb", &base, "printf", "%s", value]);
let out = cmd
.output()
.unwrap_or_else(|e| panic!("spawn wsl.exe: {e}"));
// Limpieza del base (igual que el adaptador: rm -rf en exito/error).
let _ = Command::new("wsl.exe")
.args(["-d", distro, "-e", "sh", "-c", "rm -rf \"$1\"", "quorb", &base])
.output();
out
}

fn main() -> ExitCode {
let argv: Vec<String> = std::env::args().collect();
// argv[0] = exe; argv[1] = distro; argv[2] = UN solo valor (con o sin
// espacios/comillas: el runtime MSVCRT ya lo cotizó al parsear argv).
if argv.len() != 3 {
eprintln!("uso: wsl-f14-probe <distro> <value>");
return ExitCode::from(2);
}
let distro = &argv[1];
let value = &argv[2];
let out = run(distro, value);
let exit = out.status.code().unwrap_or(-1);
let stdout_b64 = base64(&out.stdout);
let stderr_b64 = base64(&out.stderr);
println!(
"{{\"exit\":{exit},\"stdout_b64\":\"{stdout_b64}\",\"stdout_len\":{},\"stderr_b64\":\"{stderr_b64}\"}}",
out.stdout.len()
);
ExitCode::SUCCESS
}
119 changes: 119 additions & 0 deletions src-tauri/examples/wsl-wslenv-probe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! Harness WSLENV del lab W3 (issue #90) — VÍA REAL DEL ADAPTADOR.
//!
//! Sustituye al binario antiguo (`src-tauri/src/bin/wsl-wslenv-probe.rs`,
//! eliminado): ese duplicaba la política de `env_with` en un `match` y el
//! spawn seguía yendo por PowerShell. Este ejemplo:
//!
//! 1. Construye `RealWslAdapter::new()` (ProcessRunner REAL, `wsl.exe`
//! descubierto) y detecta el shell real con `shell_strategy` (F12).
//! 2. Reporta el WSLENV que produce el CÓDIGO REAL (`env_with`, la misma
//! función que `run_command` usa internamente) para el lote de
//! variables de sesión.
//! 3. Ejecuta `run_command(distro, shell, &["env"], &session)`: el remoto
//! corre el `env` constante y el harness parsea la salida en Rust.
//! Distingue AUSENTE (sin línea), VACÍO (línea `KEY=`) y PRESENTE
//! (valor) — el equivalente de `${VAR+set}`, sin confundir ausente con
//! vacía.
//!
//! Así se ejercita de verdad el camino de producción: `base_env()`,
//! `env_with()` con varias variables, `env_clear().envs()` del
//! ProcessRunner y `run_scoped()` con su cancelación acotada.
//!
//! Uso: `wsl-wslenv-probe.exe <distro> <KEY=VALUE>... --probe <KEY>...`
//!
//! Emite JSON: `{"wslenv":"<cadena>","probes":{"KEY":{"present":bool,"value":"..."}}}`.
//! Se compila como ejemplo (no entra en el producto): `cargo build -p quorb
//! --example wsl-wslenv-probe`, y vive en `src-tauri/examples/`.

use quorb_lib::wsl::{RealWslAdapter, WslAdapter};
use serde_json::{json, Value};
use std::collections::BTreeMap;
use std::process::ExitCode;

fn parse_env(raw: &str) -> BTreeMap<String, String> {
let mut map = BTreeMap::new();
for line in raw.lines() {
if let Some((k, v)) = line.split_once('=') {
// La última ocurrencia gana (como en un entorno real). Un valor
// con `=` se conserva entero (split_once); el formato `env`
// separa variables por newline, así que un valor con salto de
// línea no es representable (limitación conocida del lab).
map.insert(k.to_string(), v.to_string());
}
}
map
}

fn main() -> ExitCode {
let argv: Vec<String> = std::env::args().collect();
if argv.len() < 4 {
eprintln!("uso: wsl-wslenv-probe <distro> <KEY=VALUE>... --probe <KEY>...");
return ExitCode::from(2);
}
let distro = argv[1].clone();

let mut session: Vec<(&str, &str)> = Vec::new();
let mut probes: Vec<&str> = Vec::new();
let mut i = 2;
let mut in_probe = false;
while i < argv.len() {
match argv[i].as_str() {
"--probe" => in_probe = true,
tok if in_probe => probes.push(tok),
tok => {
let (k, v) = tok.split_once('=').unwrap_or((tok, ""));
session.push((k, v));
}
}
i += 1;
}
if session.is_empty() || probes.is_empty() {
eprintln!("uso: wsl-wslenv-probe <distro> <KEY=VALUE>... --probe <KEY>...");
return ExitCode::from(2);
}

let adapter = RealWslAdapter::new();

// WSLENV producido por el CÓDIGO REAL (env_with, la fusión que
// run_command aplica de verdad).
let merged = adapter.env_with(&session);
let wslenv = merged.get("WSLENV").cloned().unwrap_or_default();

// Shell real de la distro (F12: command -v bash).
let shell = match adapter.shell_strategy(&distro) {
Ok(s) => s,
Err(e) => {
eprintln!("shell_strategy falló para {distro}: {e:?}");
return ExitCode::from(1);
}
};

// Comando remoto CONSTANTE: `env`. Los datos (variables de sesión) van
// por el env del proceso, no interpolados en ningún script.
let output = match adapter.run_command(&distro, shell, &["env"], &session) {
Ok(o) => o,
Err(e) => {
eprintln!("run_command falló para {distro}: {e:?}");
return ExitCode::from(1);
}
};

let remote_env = parse_env(&output.stdout);
let mut probes_json = serde_json::Map::new();
for key in &probes {
let entry = match remote_env.get(*key) {
Some(v) => json!({ "present": true, "value": v }),
None => json!({ "present": false, "value": null }),
};
probes_json.insert((*key).to_string(), entry);
}

let result: Value = json!({
"distro": distro,
"shell": format!("{shell:?}"),
"wslenv": wslenv,
"probes": probes_json,
});
println!("{result}");
ExitCode::SUCCESS
}
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod window_clip;
#[cfg(target_os = "windows")]
mod windows_backdrop;
mod wm_compat;
pub mod wsl;

use config::AppConfig;
use model::UsageReport;
Expand Down
Loading
Loading