diff --git a/devlog/_fin/261010_cli_path_crossos_probe/000_plan.md b/devlog/_fin/261010_cli_path_crossos_probe/000_plan.md new file mode 100644 index 00000000000..757359a6935 --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/000_plan.md @@ -0,0 +1,62 @@ +# 000 — Cross-OS probe of the desktop-owned CLI path + +## Objective + +Find out whether the desktop-owned CLI path and install work on `dev` behaves on Linux and Windows the way it was +designed and tested on macOS, and turn every gap into a PR to `dev` or an issue. + +The work under test, all squash-merged on `dev` by 2026-10-09: + +| PR | Commit | Surface | +|---|---|---| +| #6802 | 01acdc8874 | `ocx status`/`doctor`/`resolve` treat a Desktop-supervised runtime as the authority (`src/service/desktop-supervision.mjs`) | +| #6807 | 32abcc363d | package launcher falls back to a validated PATH Bun; lifecycle version-skew notice (`bin/ocx.mjs`, `src/lib/bun-path-runtime.mjs`) | +| #6809 | 63d87f6370 | `service`/`update`/`start`/`stop` refuse to compete with a Desktop-supervised runtime | +| #6812 | 3b9fc865fa | Windows launchers preflight the selected Bun before baking it in | +| #6816 | b89bbfb083 | Desktop owns the `ocx` terminal command on PATH (`desktop/src-tauri/src/cli_command*.rs`) | +| #6818 | 3618baac41 | package launchers hand off to the Desktop `ocx`; `status`/`doctor` report PATH selection | + +The macOS proof is recorded in `devlog/_plan/261009_desktop_owned_path_cli/049_outcome.md`. That note leaves four +items to release QA: packaged click-through of the CLI page, fish on a real login, the Linux deb path, and Windows +terminal pickup after a real registry write. It also records that #6818 deliberately does not hand off on Windows +and that `desktop-supervision.mjs` returns `unsupported` on `win32`, so the #6802/#6809 authority and guards are +inert there. + +No release contains #6816 yet: v2.81.0 was published 2026-10-08, before the merge. On Linux the installer probe runs +a Desktop built from `dev` inside a disposable overlay root on lidge. On Windows a second Desktop cannot run beside +the user's live single-instance 2.77.0, so the installer is exercised at module level (`perform_in` with a real +HKCU write) and the packaged launch is recorded as unverified. + +## Hosts (baseline 2026-10-10) + +| Host | OS | Relevant state | +|---|---|---| +| `ssh lidge` | Ubuntu 24.04.4, x86_64, the login user, bash | `open-codex` 2.61.0 deb owns `/usr/bin/ocx`; Desktop not running; no `~/.opencodex-desktop`; no zsh/fish/docker/xvfb; passwordless sudo; cargo 1.95, webkit2gtk-4.1 2.52.6; bun `/usr/local/bin/bun`; GNOME session on seat0 | +| `ssh mini` | Windows 11 build 26200, the interactive user | OpenCodex Desktop 2.77.0 per-machine MSI at `C:\Program Files\OpenCodex`, running in console session 1 with its `ocx` sidecar; npm `ocx` via nvm4w at `C:\nvm4w\nodejs` which is in both Machine and User Path; bun in `~\.bun\bin`; cargo/rustc; Windows Terminal; no Visual Studio found by vswhere | + +## Constraints + +- All repository work happens in `.tmp/lanes/cli-path-crossos` (branch `codex/cli-path-crossos-probe`); the main + checkout is not touched. Fix branches are cut from `origin/dev` in `.tmp/lanes/` inside this worktree. +- Remote hosts: probe in temp directories and throwaway users where possible, never uninstall an existing tool, + back up before any persistent write, and log every mutation with its exact revert command in + `.tmp/host-changes.md` (worktree scratch, summarised in 040). +- Sol subagents do the host lanes and reviews; `create_thread` is not exposed in this session. +- Authorised: PRs to `dev` and issues. Not authorised: merge, release, version bump, deploy, messaging. + +## Work-phase map (dependency order) + +| Phase | Doc | Depends on | Closes with | +|---|---|---|---| +| wp1 | this file + 010/020/030/040 | — | roadmap reviewed | +| wp2 Linux probe | 010 | wp1 | Linux matrix with lidge evidence | +| wp3 Windows probe | 020 | wp1 | Windows matrix with mini evidence | +| wp4 fixes, issues, closeout | 040 (030 is the release-QA checklist both probes fill) | wp2, wp3 | PRs with exact-head CI, issues, reverts | + +wp2 and wp3 touch different hosts and no shared files, so they run in parallel lanes. + +## Outcome vocabulary + +Each probe row ends as **works** (observed on the host), **broken** (observed failure, with the command and output), +**by design** (behaviour matches a documented decision, e.g. Windows `windows-path-only`), or **unverified** (with +the reason it could not be observed). diff --git a/devlog/_fin/261010_cli_path_crossos_probe/001_host_baseline.md b/devlog/_fin/261010_cli_path_crossos_probe/001_host_baseline.md new file mode 100644 index 00000000000..3eb10a93536 --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/001_host_baseline.md @@ -0,0 +1,41 @@ +# 001 — Host baseline and change-log protocol (2026-10-10) + +Captured read-only before any probe mutation. Later rows in 030 compare against this. + +## lidge (Ubuntu 24.04.4, kernel 7.0.0-30, x86_64) + +- `dpkg -l open-codex` → `ii open-codex 2.61.0 amd64 OpenCodex desktop shell`; `dpkg -S /usr/bin/ocx` → `open-codex`; + `ocx --version` → `opencodex 2.61.0`. Desktop not running; `~/.opencodex-desktop` absent. +- Shell `/bin/bash`; `~/.bashrc` and `~/.profile` exist, `~/.bash_profile` and `~/.zshrc` do not; no fish, zsh, + docker or Xvfb. `unshare` and `setpriv` present; passwordless sudo. +- Bun `/usr/local/bin/bun` 1.3.14 (the launcher pins 1.4.2); Node `/usr/bin/node`; cargo/rustc 1.95 under + `~/.cargo/bin` (not on the non-interactive SSH PATH); webkit2gtk-4.1 / javascriptcoregtk-4.1 2.52.6, libsoup-3.0 3.4.4. +- GNOME session for `` on seat0/tty2. + +## mini (Windows 11 build 26200, the interactive user) + +- OpenCodex Desktop 2.77.0, per-machine MSI, `C:\Program Files\OpenCodex\`, running in console session 1 + (`opencodex-desktop` PID 22680, `ocx` sidecar PID 33476); `~\.opencodex-desktop` absent. +- npm `ocx` 2.58.0 via nvm4w: `C:\nvm4w\nodejs\ocx{,.cmd,.ps1}`; `C:\nvm4w\nodejs` is in both Machine Path and + User Path; HKCU Path is `REG_EXPAND_SZ`. +- Bun `%USERPROFILE%\.bun\bin\bun.exe`; cargo/rustc stable `x86_64-pc-windows-msvc` (links despite `vswhere -latest` + listing nothing); Windows Terminal `wt.exe`; PowerShell 7 in Path. + +## Temp clones + +Both hosts carry a clone of `d17a9f2239` with CI's empty sidecar/resource stubs (ci.yml:1567-1573): +`/tmp/ocx-probe-261010/repo` (lidge) and `%TEMP%\ocx-probe-261010\repo` (mini). + +## Change-log protocol + +Every remote mutation is appended to the worktree scratch file `.tmp/host-changes.md` (gitignored) BEFORE it is +made: time (KST), host, exact command, exact revert command, and the check that proves the revert. 040's closeout +copies the final table with each row marked reverted or kept (with reason). Secrets, tokens and request bodies never +enter the log. + +## First evidence + +- lidge `cargo test --manifest-path desktop/src-tauri/Cargo.toml cli_command`: run 1 `45 passed; 1 failed; 2 ignored` + (`final_save_before_journal_deletion_is_cleaned_on_reopen`, `Err("lock-busy")` at `cli_command_record.rs:1114`); + runs 2-4 `46 passed; 0 failed; 2 ignored`; isolated test 5/5 pass. +- mini, same command: `27 passed; 0 failed; 0 ignored`. diff --git a/devlog/_fin/261010_cli_path_crossos_probe/010_linux_probe.md b/devlog/_fin/261010_cli_path_crossos_probe/010_linux_probe.md new file mode 100644 index 00000000000..44bcccc154a --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/010_linux_probe.md @@ -0,0 +1,67 @@ +# 010 — wp2: Linux probe on `ssh lidge` + +Source under test: a temp clone of `d17a9f2239` at `/tmp/ocx-probe-261010/repo` on lidge. Every host mutation is +appended to the worktree's `.tmp/host-changes.md` with its revert before it is made. + +## Code facts the probe relies on (read 2026-10-10) + +- Install kind: `desktop/src-tauri/src/cli_command_posix.rs:147` rejects debug builds (`development-launch`), + AppImage env or `/tmp/.mount_` paths (`temporary-bundle`) and any executable whose canonical parent is not + `/usr/bin` (`unpackaged-launch`); a qualifying launch needs an executable `/usr/bin/ocx` and records `linux-deb`. + dpkg is never consulted. +- Owned paths: `~/.opencodex-desktop/{bin/ocx (0700), path.sh (0600), cli.json, cli.lock, journal/, backups/}`. +- Managed blocks (`# >>> OpenCodex Desktop ocx PATH v1 >>>`): `.bashrc` plus the FIRST existing of + `.bash_profile`/`.bash_login`/`.profile` (none created; absent → `login-file-absent`, partial); `.zshrc` and + `.zlogin` under `ZDOTDIR` or HOME; `${XDG_CONFIG_HOME:-~/.config}/fish/config.fish` wrapped in + `if status is-interactive` (`cli_command_posix.rs:97-132`). +- The ignored `real_shell_selects_desktop_shim_on_temp_home` test covers bash and zsh only, never fish. +- Launcher handoff (`src/lib/desktop-cli-handoff.mjs`): escape hatch and recursion guard `OCX_NO_DESKTOP_HANDOFF=1`; + update/uninstall/remove/internal commands are excluded; record must be 0700 dir / 0600 file owned by the euid. +- PATH-Bun fallback (`src/lib/bun-path-runtime.mjs`) needs Bun ≥ the pinned 1.4.2; lidge's `/usr/local/bin/bun` + is 1.3.14 and must be rejected. + +## Probe steps + +| # | Step | Command sketch | Expected | +|---|---|---|---| +| L1 | Rust unit tests | `cargo test --manifest-path desktop/src-tauri/Cargo.toml cli_command` with CI's empty sidecar/resource stubs (ci.yml:1567-1573), repeated ×5 | all pass (first run: 1 `lock-busy` flake, see F1) | +| L2 | Ignored real-shell test | `... cli_command_posix::tests::real_shell_selects_desktop_shim_on_temp_home -- --ignored --exact --nocapture` | bash passes; zsh skipped (absent) | +| L3 | Build dev deb | `bun install` (root, gui, desktop) → `bun run build:gui` → `bun desktop/scripts/prepare-sidecar.ts --target x86_64-unknown-linux-gnu` → in `desktop/`: `bunx tauri build --ci --bundles deb --config '{"bundle":{"createUpdaterArtifacts":false}}'` | `.deb` under `src-tauri/target/release/bundle/deb/` | +| L4 | Throwaway user | `sudo useradd -m -s /opt/ocx-probe/fish/bin/fish ocxprobe` after unpacking `fish-4.9.3-linux-x86_64.tar.xz` into `/opt/ocx-probe/fish`; give it `.bashrc` and `.profile` copied from `/etc/skel` | user exists, `getent passwd ocxprobe` shows fish | +| L5 | Install dev deb | download v2.61.0 deb for revert first; `sudo apt install ./OpenCodex-*.deb` | `/usr/bin/opencodex-desktop` + `/usr/bin/ocx` from dev | +| L6 | Desktop reconcile as ocxprobe | run `/usr/bin/opencodex-desktop` as ocxprobe under a virtual display (`xvfb-run` installed and logged, or the user's Wayland/X session if Xvfb is refused) long enough for `reconcile_on_launch`, then stop it | `~ocxprobe/.opencodex-desktop/cli.json` enabled, kind `linux-deb`; blocks in `.bashrc`, `.profile`, `.zshrc`, `.zlogin`, `config.fish` | +| L7 | Real login shells | `sudo -iu ocxprobe` (fish login, interactive via `script -qc`), `fish -l -c 'command -v ocx'`, `bash -l -i -c`, `bash -lc`, `env -i ... bash --login`, `ssh ocxprobe@localhost` if keys allow | interactive fish/bash → `~/.opencodex-desktop/bin/ocx`; non-interactive fish → `/usr/bin/ocx` (guard) | +| L8 | Package launcher handoff | as ocxprobe: `npm i -g` of a `npm pack` tarball into `~/.npm-global`, `bun link` from the clone, and a linuxbrew-style `~/.linuxbrew/bin/ocx` symlink; run each `--version`/`status`; repeat with `OCX_NO_DESKTOP_HANDOFF=1` | handoff to the record target; escape hatch runs the package | +| L9 | Diagnostics | `ocx status`, `status --json`, `doctor`, `resolve --json` with and without the Desktop running | `ocx command:` line names Desktop first/not first; supervisor line only with Desktop live | +| L10 | #6809 guards | with dev Desktop running as ocxprobe: package `ocx start`, `stop`, `service install`, `update --check`-style dry paths | refusals naming Desktop | +| L11 | Bun fallback | temp package copy with bundled Bun removed; PATH first = Bun 1.4.2 in a temp dir; then 1.3.14 | 1.4.2: `using PATH Bun` notice; 1.3.14 rejected with Desktop CLI named | +| L12 | Opt-out and revert | Desktop "Remove" / disabled reconcile as ocxprobe; then `sudo userdel -r ocxprobe`, `sudo apt install ./OpenCodex-2.61.0-linux-amd64.deb` (allow downgrade), remove `/opt/ocx-probe`, `/tmp/ocx-probe-261010` | blocks removed except user-modified ones; host back at 2.61.0 | + +## Outcomes + +Each row is recorded in 030's matrix as works / broken / by design / unverified with the command and output tail. + +## Reflection (same architect, Dewey 01a1225a-9e23): MISALIGNED → dispositions + +These replace the matching rows above. + +- **L1/F1:** the first-run `lock-busy` failure stays recorded as evidence. The forked-child cause is a hypothesis. + Before any fix, B reproduces under a loop (`for i in $(seq 30)`), captures `RUST_BACKTRACE=1` and the errno from + `flock`, and identifies the holder with `/proc/locks` plus `ls -l /proc/*/fd` matching the lock inode during a + failure (an instrumented copy of the test may sleep on failure to allow the snapshot). The fix shape is chosen + only from that evidence. +- **L7:** positive fish case is `fish -l -i -c 'command -v ocx'` and an interactive login via `script -qc 'sudo -iu ocxprobe' /dev/null`; + the negative case is `env -i HOME=/home/ocxprobe PATH=/usr/bin:/bin /opt/ocx-probe/fish/bin/fish -l -c 'command -v ocx'`. + Bash: `env -i HOME=/home/ocxprobe PATH=/usr/bin:/bin bash --login -c 'command -v ocx'` and with `-i`. +- **L10:** no live `update`. The guard probe runs `start`, `stop` and `service install` from the package as ocxprobe + only while the dev Desktop runs as ocxprobe with ocxprobe's own `~/.opencodex`, and the focused existing tests + (`tests/update/update-desktop-owner.test.ts`, `tests/service/service-ownership-handover.test.ts`, + `tests/cli/cli-stop-json.test.ts`) run on lidge for `update`. +- **L11:** set `OCX_NO_DESKTOP_HANDOFF=1`; remove the bundled Bun and the installer from the temp package copy; the + 1.3.14 expectation is "rejected, no Desktop CLI named" because `findDesktopCli` returns null off macOS (candidate + finding F4). +- **L8/L12:** npm-global uses `npm_config_prefix=/home/ocxprobe/.npm-global`. Before L5, download the v2.61.0 deb to + `/opt/ocx-probe/revert/`, verify it against its `.sha256`, and record `dpkg-deb -f Package Version`; L12 + restores with `sudo apt install --allow-downgrades /opt/ocx-probe/revert/OpenCodex-2.61.0-linux-amd64.deb`. + +See the audit dispositions at the end of 020_windows_probe.md; items 1 and 5 replace L4, L5, L7 and L12 host steps. diff --git a/devlog/_fin/261010_cli_path_crossos_probe/011_wp2_execution.md b/devlog/_fin/261010_cli_path_crossos_probe/011_wp2_execution.md new file mode 100644 index 00000000000..e957c082d16 --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/011_wp2_execution.md @@ -0,0 +1,101 @@ +# 011 — wp2 execution: three Linux lanes on lidge + +Re-verified against `origin/dev` 93ed1a40b4: the two commits after `d17a9f2239` (#6846, #6848) touch no file under +test, so 010 and its dispositions stand. Architect proposal (Dewey, 01a1225a-9e23) D1-D6 accepted as below. + +## Host hazards found in P + +- lidge runs the user's own npm `ocx` proxy (PID 3280942) on `127.0.0.1:10100`. No probe may reach, stop or replace + it: every Desktop and `ocx` probe runs in a loopback-only network namespace, and each lane re-checks that PID and + the port listener before and after. +- `/`, `/var/tmp`, `/tmp` and `/home` share one ext4 filesystem, so an overlay upper directory on `/var/tmp` overlaps + `lowerdir=/`; upper and work go on a tmpfs mounted inside the private mount namespace (D3). +- 's umask is 002; the PATH-Bun fallback skips group-writable Bun files or directories (D6). + +## Decisions + +| ID | Decision | Disposition | +|---|---|---| +| D1 | Live Desktop checks (L9 with Desktop, L10 refusals) run in lane A, the only place a dev Desktop exists; lane B uses fixture records | accepted | +| D2 | Each lane has its own clone and `BUN_INSTALL`, `BUN_INSTALL_CACHE_DIR`, `npm_config_cache` under `/tmp/ocx-probe-261010/`; only `~/.cargo` is shared (cargo locks it) | accepted | +| D3 | Overlay of `/` with tmpfs upper/work inside `sudo setsid -f unshare --mount --pid --fork --propagation private`, fresh `proc`, rbind `/dev` `/sys`, tmpfs over `/tmp` `/run` `~`; enter with `nsenter -t -m -p -r -w` | accepted | +| D4 | Loopback-only network namespace for every Desktop/`ocx` probe; Desktop as `ocxprobe` under `dbus-run-session -- xvfb-run -a` with `WEBKIT_DISABLE_DMABUF_RENDERER=1`; apt inside the overlay with a `policy-rc.d` returning 101 | accepted | +| D5 | `bun install` with the host Bun, then the repo's `node_modules/bun/bin/bun.exe` (1.4.2) for `build:gui`, `prepare-sidecar` and `x tauri build` | accepted | +| D6 | L11 temp Bun directory and binary `chmod 755` | accepted | + +## Lanes + +| Lane | Rows | Workspace | Returns | +|---|---|---|---| +| A — packaged deb | L3, L5-L7, live L9/L10, L12; 020 audit items 1 and 5 | clone `/tmp/ocx-probe-261010/a`; overlay namespace | namespace PIDs and mounts; `dpkg -s open-codex` inside vs outside; `cli.json`; each rc diff; LOGIN/INTERACTIVE/`command -v ocx` per shell mode; status/doctor/resolve with Desktop live; guard refusal text; Desktop log tail; revert checks | +| B — launcher and diagnostics | L8, L9 without Desktop, L10 focused tests, L11, F4 | clone `/tmp/ocx-probe-261010/b`; temp HOMEs on the real root; probes inside a loopback netns as with `env -i` and a PATH without `~/.local/bin` | `--version` through npm-global (`npm_config_prefix`), bun-link and a linuxbrew-shaped path, with and without `OCX_NO_DESKTOP_HANDOFF=1`; record-state table (missing, disabled, ready, unsafe mode, pending, oversize, target missing, target is self) with status/doctor lines; resolve output; L11 notice and the 1.3.14 failure text; focused test results with the repo Bun | +| C — F1 lock-busy | L1, L2, F1 | existing `/tmp/ocx-probe-261010/repo` | pass/fail counts for 30 parallel and 30 `--test-threads=1` runs with load average; on failure errno and holder process from a `/proc/*/fd` inode scan during an instrumented 3 s sleep; cause verdict; L2 ignored real-shell result | + +## Stop conditions + +- Any lane: the host proxy PID or the `:10100` listener changes, `dpkg -s open-codex` stops reporting 2.61.0, or + `/etc/passwd` or a file in `~` changes → stop and revert. +- A: overlay mount fails → stop and report; Desktop not up in 120 s after one software-rendering retry → row + unverified per audit item 1. +- B: any probe reaches port 10100 → stop. +- C: 60 runs without a failure → "not reproduced", no fix PR for F1 (the first-run failure stays recorded). +- Revert: kill the namespace holder, require `lsns -t mnt` and `findmnt` free of `ocx-probe`, then + `sudo rm -rf /var/tmp/ocx-probe`; lanes B/C remove their `/tmp/ocx-probe-261010/` and any netns they created. + +Every mutation is logged in the worktree `.tmp/host-changes.md` (via the coordinator) before it happens. + +## Reflection (Dewey, same architect): MISALIGNED → dispositions + +1. The namespace holder runs `exec chroot $M sleep infinity`; every later step enters with + `sudo nsenter -t -m -p -r -w`, so the root is the overlay (D3 corrected). +2. Before the holder starts, `sudo mkdir -p /var/tmp/ocx-probe /var/tmp/ocx-probe-evidence` is logged as a host change. + Inside the namespace, `/var/tmp/ocx-probe-evidence` is bind-mounted to `$M/evidence` so lane A's results survive the + tmpfs teardown, and the overlay's dangling `/etc/resolv.conf` symlink is replaced by the host file's contents. +3. The network namespace (`ip netns add ocxprobe261010`, loopback up) is created after apt and the fish download, kept + alive by its own named namespace, and every Desktop, status, doctor, resolve and guard probe runs in that one + namespace (`ip netns exec ocxprobe261010 nsenter ...` or the reverse order, whichever works with the chroot). + Revert: `sudo ip netns delete ocxprobe261010`. +4. Lane B uses the pinned Bun for `bun install` and `build:gui` before `npm pack` (`prepack` → `prepare:package`). +5. Stop condition watches named paths only: `~/.bashrc`, `~/.profile` (sha256 recorded first), `~/.opencodex` (mtime of + its top-level files), absence of `~/.opencodex-desktop`, `~/.local/lib/node_modules/@bitkyc08`; `~/.cargo` is shared + by design. +6. Revert check: record the holder PID and `readlink /proc//ns/mnt` at start; revert requires the PID gone and that + inode absent from `sudo lsns -t mnt` before `sudo rm -rf /var/tmp/ocx-probe`. + +## Audit (independent Sol, 01a1226f-82b1): FAIL → dispositions + +These supersede earlier rows where they conflict. + +1. **Fixed entry and gate.** Lane A creates the netns first on the host (`sudo ip netns add ocxprobe-a`, + `sudo ip -n ocxprobe-a link set lo up`; no other interfaces), and the mount/PID holder is started *inside* it: + `sudo ip netns exec ocxprobe-a setsid -f unshare --mount --pid --fork --propagation private sh -c '; exec chroot $M sleep infinity'`. + Network access for apt and the fish tarball is done before that, by downloading on the host into + `/var/tmp/ocx-probe-evidence/in/` (debs via `apt-get download` of xvfb and its deps, fish tarball, the dev deb) and + installing inside the overlay from those files with `dpkg -i` only. Every later command enters with exactly + `sudo nsenter -t -m -p -n -r -w ` and is preceded by a gate script that aborts unless: + `readlink /proc/self/ns/mnt` and `/proc/self/ns/pid` equal the holder's recorded inodes, `/proc/self/ns/net` + equals `ocxprobe-a`'s inode, `ip -o link` lists only `lo`, and `test -f /evidence/.overlay-marker` (a file written + only into the overlay at setup). Lane B uses its own `ocxprobe-b` netns with the same gate minus the overlay checks. +2. **Deb route.** The dev deb is copied from lane A's host clone to `/var/tmp/ocx-probe-evidence/in/` with its sha256 + recorded, then installed inside the overlay from `/evidence/in/`. Before and after: host `dpkg -s open-codex` + reports 2.61.0 and `sha256sum /usr/bin/ocx` on the real root is unchanged. +3. **Evidence and rollback.** Evidence is copied back with `rsync -a lidge:/var/tmp/ocx-probe-evidence/out/ + /.tmp/evidence/lidge-a/`; then the holder is killed, its mount-ns inode must be absent from `sudo lsns -t mnt`, + `sudo ip netns delete ocxprobe-a`, `sudo ip netns delete ocxprobe-b`, + `sudo rm -rf /var/tmp/ocx-probe /var/tmp/ocx-probe-evidence`, and + `ls -d /var/tmp/ocx-probe* /tmp/ocx-probe-261010` must fail; `ip netns list` must not show either name. + A rerun must first confirm that `/var/tmp/ocx-probe` and `/var/tmp/ocx-probe-evidence` do not exist (or use fresh + `mktemp -d` paths): `mkdir -p` accepts existing directories, and this cleanup removes everything under the fixed paths. +4. **Supervision gate.** Live L9/L10 coverage is claimed only if `resolve --json` (or a direct + `inspectDesktopSupervision()` call) inside the namespace reports `kind: "desktop"` for the Desktop's child; + otherwise those rows are recorded unverified with the inspector's actual result. + +5. **Root proof.** The gate requires `stat -f -c %T /` = `overlayfs` and `test -f /.ocxprobe-overlay-root`, a marker + written to `$M/.ocxprobe-overlay-root` after the overlay mount (it lives only in the tmpfs upper layer); the + `/evidence` marker is dropped. +6. **Per-path ownership and cleanup.** Each lane removes only what it owns, after it finishes, and checks each path + separately with `test ! -e `: + - lane A: `/tmp/ocx-probe-261010/a`, netns `ocxprobe-a`, holder ns, `/var/tmp/ocx-probe`; + - lane B: `/tmp/ocx-probe-261010/b`, netns `ocxprobe-b`; + - lane C: `/tmp/ocx-probe-261010/repo` and `/tmp/ocx-probe-261010/c`; + - coordinator, after all three: `/var/tmp/ocx-probe-evidence` (after rsync) and the now-empty `/tmp/ocx-probe-261010`. diff --git a/devlog/_fin/261010_cli_path_crossos_probe/019_wp2_outcome.md b/devlog/_fin/261010_cli_path_crossos_probe/019_wp2_outcome.md new file mode 100644 index 00000000000..9b8c1d0bdb7 --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/019_wp2_outcome.md @@ -0,0 +1,27 @@ +# 019 — wp2 outcome: Linux on lidge + +Source `d17a9f2239` (package 2.82.0). Three Sol lanes ran in parallel under 011; every host mutation was logged and +reverted. Raw evidence stays in the worktree scratch `.tmp/evidence/lidge-{a,b,c,shared}/` (not committed). + +| Row | Result | Evidence | +|---|---|---| +| L1 Rust `cli_command` tests | **works** — 60/60 runs `46 passed; 0 failed; 2 ignored` (30 parallel, 30 `--test-threads=1`, load 0.04-0.66) | lane C `results.txt` | +| F1 first-run `lock-busy` | **unverified** — not reproduced in 60 runs; the single first-run failure (`final_save_before_journal_deletion_is_cleaned_on_reopen`, 001) stays unexplained; no fix PR per the 011 stop rule | lane C | +| L2 ignored real-shell test | **broken (test)** — exit 101, `bash: expected exactly two stdout lines`. Ubuntu's `/etc/bash.bashrc:44-53` prints the sudo hint to stdout for a sudo-group user without `~/.hushlogin`; the shell itself resolved `/usr/bin/ocx` then `npm-ocx` correctly. Test assumption at `cli_command_posix.rs:1063-1066` → F5 | lane C `l2-real-shell.log`, `bash-diagnostic.log` | +| L3 dev deb build | **works** — pinned Bun 1.4.2 `build:gui` → `prepare-sidecar` → `tauri build --bundles deb` produced `OpenCodex_2.82.0_amd64.deb`, whose sha256 was recorded when it was staged and matched at install | lane A `02_build.sh`, `07_gate_deb.log`, `08_install.log`; the raw build log was deleted with the clone before it was copied, so `02_build_observed_tail.md` is a transcription | +| L5/L6 deb install + reconcile | **works** — inside the overlay `dpkg -i` → 2.82.0 (host stayed 2.61.0); Desktop under `dbus-run-session -- xvfb-run -a` wrote an enabled `cli.json` with a bundle (the `kind` value itself was not printed; on Linux only `posix::stable_bundle` accepting a `/usr/bin` launch produces one, and it records `linux-deb`), `status --json` `cliCommand.configured: true`, shim 0700, `path.sh` 0600, and managed blocks in `.bashrc`, `.profile`, `.zshrc`, `.zlogin`, `.config/fish/config.fish` matching the generated text exactly | lane A `08_install.log`, `10_reconcile_readout.log`, `13b_diagnostics.log`, `15_rc_exact.log` | +| L7 real login shells (release QA: fish) | **works** — real fish login via `script -qec 'su - ocxprobe'`: login 0, interactive 0, `command -v ocx` → `~/.opencodex-desktop/bin/ocx`; `fish -l -i -c` same; clean-PATH `fish -l -c` interactive 1 → `/usr/bin/ocx` (**by design**, `if status is-interactive` guard); bash interactive login and `bash -lc` → shim | lane A `11_fish_matrix.log`, `12_bash_matrix.log` | +| L8 package launcher handoff | **works** — npm-global (`npm_config_prefix`), `bun link` and a linuxbrew-shaped symlink all hand off to the record target with a ready record; `OCX_NO_DESKTOP_HANDOFF=1` runs the package (`opencodex 2.82.0`); unsafe mode, symlinked record, pending and oversize records are refused with explicit errors | lane B `matrix.log` | +| L9 diagnostics | **works** — without Desktop: `status`/`--json` name the npm launcher first, `.cliCommand.desktopFirstOnPath=false` with `path-first-not-desktop`, doctor shows it, `resolve --json` `runtime-absent`; with the dev Desktop live in the overlay: Desktop first on PATH, `startupSource: live`, `resolve --json` `supervisor.kind: "desktop"` | lane B `matrix.log`; lane A `13b_diagnostics.log` | +| L10 refuse-to-compete (#6809) | **works** — with `supervisor.kind=desktop`: `ocx start` refuses, `ocx service install` refuses and leaves supervision intact; `ocx stop` stops the child and warns the Desktop may restart it (**by design**, `src/cli/desktop-runtime-guidance.ts:27`, `tests/cli/cli-stop-json.test.ts:279`; 010's "refuse" expectation was wrong). Focused tests: 517 pass / 4 skip / 0 fail across the 11 files | lane A `14b_guards.log`, `24_service_guard.log`; lane B `tests-final.log` | +| L11 PATH-Bun fallback (#6807) | **works** — bundled Bun and installer removed, Bun 1.4.2 first on PATH: `opencodex: using PATH Bun 1.4.2.` and `opencodex 2.82.0`; with only 1.3.14: `Bun binary missing after install attempt.` (exit 1) | lane B `fallback.log` | +| F4 Linux failure text names no Desktop CLI | **by design today** — `findDesktopCli()` is macOS-only (`src/lib/bun-path-runtime.mjs:80`), so a broken package launcher on a deb host does not point at `/usr/bin/ocx`; naming it safely needs a check that `/usr/bin/ocx` is the deb's binary and not an npm `--prefix /usr` symlink, so it is a follow-up, not a fix here | lane B | +| L12 opt-out | **unverified** — the CLI page's Remove/disable action is not reachable without a GUI operator; cleanup of the probe itself verified | lane A | +| Packaged CLI page click-through | **unverified** — same reason | — | + +Host state after wp2 (coordinator check): `/var/tmp/ocx-probe*`, `/tmp/ocx-probe-261010`, `~/.opencodex-desktop` absent; +no `ocxprobe*` netns; no `ocxprobe` user; `open-codex` 2.61.0; user proxy PID 3280942 still on `127.0.0.1:10100`. + +Process note: the first dispatch of lanes A and B was tagged as reviewers by codexclaw's keyword role inference +(packet text contained "audit"/"verify") and refused to execute; they were redispatched with a `CXC-ROLE: explorer` +header. diff --git a/devlog/_fin/261010_cli_path_crossos_probe/020_windows_probe.md b/devlog/_fin/261010_cli_path_crossos_probe/020_windows_probe.md new file mode 100644 index 00000000000..58f2b12188b --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/020_windows_probe.md @@ -0,0 +1,107 @@ +# 020 — wp3: Windows probe on `ssh mini` + +Source under test: a temp clone of `d17a9f2239` at `%TEMP%\ocx-probe-261010\repo` on mini. The live Desktop 2.77.0 +(per-machine MSI, `C:\Program Files\OpenCodex`, PID 22680, session 1) and nvm4w npm `ocx` 2.58.0 are user tools and +are not reinstalled, stopped or upgraded. + +## Code facts (read 2026-10-10) + +- `cli_command_windows.rs:693` `stable_bundle` rejects debug launches and paths containing `target`, `temp`, `tmp`; + needs a regular non-symlink sibling `ocx.exe`. Any install root qualifies, including Program Files. +- `prepend` (600) puts the install directory first in `HKCU\Environment\Path`, preserving raw text and REG_SZ vs + REG_EXPAND_SZ; `machine_conflict` (805) flags any Machine Path dir holding `ocx.exe/.com/.cmd/.bat`; `broadcast` + (848) sends `WM_SETTINGCHANGE`/`Environment` with a 1 s per-recipient timeout; `notifyPending` persists failure. +- No test writes the real HKCU; the Windows Path tests are pure transforms or use an in-memory registry seam. +- `src/lib/desktop-cli-handoff.mjs:19` returns `windows-path-only`; `src/service/desktop-supervision.mjs:75` returns + `unsupported` for anything but darwin/linux, so #6802 authority and #6809 refusals never engage on Windows. + +## Probe steps + +| # | Step | Expected | +|---|---|---| +| W1 | `cargo test --manifest-path desktop\src-tauri\Cargo.toml cli_command` with CI stubs, ×3 | pass (first run 27/27) | +| W2 | Baseline: `reg query HKCU\Environment /v Path` (type), `reg export HKCU\Environment .reg /y`, `Get-Command ocx -All`, `where ocx` | backup file recorded in host log | +| W3 | Real HKCU write through the real module: a throwaway `#[ignore]` test added only in the temp clone that runs the Windows `plan` → `apply_change` → `notify` path for a staging dir `%USERPROFILE%\ocx-probe-desktop\` (no temp/tmp in path) holding a copy of `C:\Program Files\OpenCodex\ocx.exe` | HKCU Path gains the staging dir first, type preserved; issues include `machine-path-conflict` | +| W4 | New terminal pickup: (a) scheduled task `/IT` in session 1 running `cmd /c "echo %PATH% & where ocx" > out.txt`; (b) `explorer.exe ` launched via an /IT task so Explorer's refreshed environment is used; (c) `wt new-tab --reloadEnvironment` | staging dir present in PATH of each new process; `where ocx` still lists `C:\nvm4w\nodejs\ocx.cmd` first (machine conflict) | +| W5 | Counter-probe without the machine conflict: same harness, but compute selection with a PATH where Machine entries lack ocx (simulated by the module's own selection on a copy, not by editing Machine Path) | Desktop dir would win; documents that the conflict is the only blocker | +| W6 | Revert W3 through the module's removal path, then compare `reg query` with the backup | HKCU Path byte-identical to baseline; otherwise `reg import` after checking for unrelated edits | +| W7 | Diagnostics with new code: from the clone, `bun src\cli\index.ts status --json`, `doctor`, `resolve --json` against the live Desktop runtime (read-only), with and without a hand-made 0600-equivalent `cli.json` record in a temp USERPROFILE | `desktopSupervision: unsupported`; status recommends service actions despite Desktop supervising (gap); `packageHandoff=disabled-on-windows`; `path-first-not-desktop` | +| W8 | #6809 guards in isolation: `OPENCODEX_HOME`, `CODEX_HOME` in temp, free port; start a runtime from the Desktop's `ocx.exe` copy as a fake "Desktop child", then package `start`/`stop` from the clone against that isolated home | no refusal on Windows (gap evidence); never against the live home | +| W9 | #6812 Bun preflight: focused tests `tests/lib/bun-runtime-preflight.test.ts`, `tests/service/service-runtime-preflight.test.ts`, `tests/codex-integration/codex-shim-runtime-preflight.test.ts` on mini; plus `OPENCODEX_BUN_PATH` pointing at a non-Bun exe in temp with an isolated home | tests pass; bogus Bun rejected before baking | +| W10 | Launcher Bun fallback (#6807) on Windows: temp package copy, bundled Bun absent, PATH Bun 1.4.2 vs the user's `~\.bun\bin\bun.exe` version | 1.4.2 accepted with notice | + +Do not run against the live install: package `ocx start`, `stop`, `service install/repair/start/restart/stop`, +`update`, `doctor --reclaim`. + +## Reflection (same architect, Boyle 01a1225a-9ed0): MISALIGNED → dispositions + +These replace the matching rows above. + +- **W3/W6 contract:** the harness proves the real registry write, the broadcast and the persisted debt, so it goes + through the same entry the Desktop uses: `Store::transact` with the Windows plan for a bundle produced by + `stable_bundle` on the staging directory (copied `opencodex-desktop.exe` + sibling `ocx.exe`), with HOME/USERPROFILE + pointed at a temp record root for the store. If `stable_bundle` refuses the staging copy, that refusal is recorded + and the bundle is built by hand for the write probe only. Restoration runs in a `finally`-style guard (Drop) that + calls the removal path; W6 compares raw bytes and value type from `RegQueryValueExW` + (`Get-Item HKCU:\Environment` `.GetValue('Path',$null,'DoNotExpandEnvironmentNames')` + `GetValueKind`) against + the values saved in W2, and falls back to setting the saved raw value and type, never `reg import` of the whole key. +- **W4:** the scheduled task runs a pre-written `probe.cmd` (PATH evaluated at run time, not creation), writing + `%PATH%`, `where ocx` and its own parent PID/image/session (via PowerShell `Get-CimInstance Win32_Process`). + The Explorer variant launches `explorer.exe probe.cmd` from an /IT task and records the parent chain; `wt new-tab + --reloadEnvironment` runs from an /IT task in session 1. SSH `cmd` is reported only as the stale control. +- **W5:** labelled simulated: a pure PATH-order computation over Machine then User entries after the write. +- **W7:** expectations use real fields: `status --json` `startupSource` and `cliCommand`; `resolve --json` + `supervisor.kind="unsupported"` when live. A hand-made record is labelled synthetic. Service-action + recommendations are recorded only if status reports the runtime unhealthy. +- **W8:** no copied sidecar. Evidence is the direct inspector result on win32 (`inspectDesktopSupervision()` → + `unsupported` while the real Desktop child is live) and the focused guard tests on mini; any lifecycle simulation is + isolated from client homes too. F2 separates the supervision gap from the deliberate PATH-only handoff choice. +- **W9:** the shim test is `tests/codex-integration/codex-shim-runtime-preflight.test.ts`. W10 first reads the pinned + Bun policy in `src/lib/bun-path-runtime.mjs:67`. + + +## Audit (independent Sol, 01a12262-abb4): FAIL → dispositions + +These supersede the earlier rows and the reflection dispositions where they conflict. + +1. **Disposable Linux root (replaces L4, L5, L12 host steps).** The dev deb is never installed on lidge's real root. + B builds a private overlay of `/` and does everything that needs `/usr/bin` inside it: + `sudo unshare --mount --pid --fork --propagation private` → `mount -t overlay overlay -o lowerdir=/,upperdir=/var/tmp/ocx-probe/upper,workdir=/var/tmp/ocx-probe/work /var/tmp/ocx-probe/merged` + → mount fresh `proc`, bind `/dev`, `/dev/pts`, `/sys`, a tmpfs `/tmp`, and copy `/etc/resolv.conf` → `chroot`. + Inside: `dpkg -i` the dev deb (replacing 2.61.0 only in the overlay), `apt-get install -y xvfb dbus-x11` if needed, + unpack portable fish 4.9.3 to `/opt/fish`, `useradd -m -s /opt/fish/bin/fish ocxprobe` with `/etc/skel` files. + The host dpkg database, `/usr/bin/ocx`, `/etc/passwd` and real HOMEs are never written. Revert: exit the + namespace (its mounts vanish with it) and `sudo rm -rf /var/tmp/ocx-probe`. Verification of the revert: + `dpkg -s open-codex | grep Version` = 2.61.0, `getent passwd ocxprobe` empty, `mount | grep ocx-probe` empty. + If the overlay cannot run the Desktop (WebKit, dbus, display), the deb row is recorded as unverified with the + error and the install-kind evidence falls back to the overlay's `dpkg -i` plus a direct call of + `stable_bundle` through the Desktop's `ocx` path checks. +2. **Windows integrated launch.** The Desktop uses `tauri-plugin-single-instance` (`desktop/src-tauri/src/lib.rs:298`), + so a second dev Desktop on mini would hand its arguments to the live 2.77.0 instance; launching one is not safe or + meaningful. The Windows matrix therefore states "packaged Desktop launch: unverified (single instance with the + user's live Desktop)" and records module-level evidence: the throwaway harness calls `perform_in` + (`cli_command.rs:165`), the same core `perform` and `reconcile_on_launch` use, with `observe = stable_bundle(, false, )`, a `Store` under a temp USERPROFILE-like root, and `Action::Reconcile`. + Only `reconcile_on_launch`'s Tauri scheduling and `perform`'s home lookup stay outside the harness. +3. **Windows Terminal observation inside the tab.** The /IT task runs + `wt.exe -w new new-tab --reloadEnvironment cmd /c %USERPROFILE%\ocx-probe-desktop\probe.cmd wt`; `probe.cmd` + writes `%PATH%`, `where ocx` and a PowerShell dump of its own process, parent and grandparent (name, PID, + SessionId) to `probe-wt.txt`, then exits. Controls, each with its own output file: `explorer.exe probe.cmd` from + an /IT task (parent must be the logon explorer.exe), a plain /IT task running `probe.cmd` (Task Scheduler builds a + fresh environment), and SSH `cmd` (stale). Before the write, the same four probes run once as the baseline. +4. **Windows revert contract.** Order: (a) W2 saves the raw Path value and kind via + `.GetValue('Path',$null,'DoNotExpandEnvironmentNames')`/`GetValueKind('Path')` to a file, plus `reg export` as a + second copy; (b) after the probes the harness calls `perform_in(..., Action::Remove, ...)`, which saves the record + off before removal (`cli_command.rs:179-201`) and broadcasts; (c) read the value again: identical raw text and + kind → done; otherwise diff it against (a) and, if the only difference is the owned entry, set the saved raw value + with the saved kind and broadcast `WM_SETTINGCHANGE` (PowerShell `SendMessageTimeout` P/Invoke); any other + difference is left for the user and reported. Cleanup list: each scheduled task gets a unique logged name + (`ocx-probe-261010--`) removed with `schtasks /Delete /TN /F`, close no user + windows (each probe exits itself), delete `%USERPROFILE%\ocx-probe-desktop`, the temp record root and + `%TEMP%\ocx-probe-261010`. +5. **Fish real login inside the session.** `printf 'status is-login; and echo LOGIN; status is-interactive; and echo INTERACTIVE; command -v ocx; echo $PATH; exit\n' | script -qec 'su - ocxprobe' /dev/null` + inside the overlay chroot; the same for bash by switching the probe user's shell (`usermod -s /bin/bash`) and + re-running; `fish -l -i -c` stays as a separate control and the clean-PATH `fish -l -c` as the negative control. + +Non-blocking folded: F1's first row is a hypothesis until wp2 evidence (reflection dispositions); F3 depends on the +observed W3/W4 order; F4 depends on L11's failure text. diff --git a/devlog/_fin/261010_cli_path_crossos_probe/021_wp3_execution.md b/devlog/_fin/261010_cli_path_crossos_probe/021_wp3_execution.md new file mode 100644 index 00000000000..1ac5d1429f4 --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/021_wp3_execution.md @@ -0,0 +1,88 @@ +# 021 — wp3 execution: two Windows lanes on mini + +Re-verified against `origin/dev` 93ed1a40b4 (no tested file changed since `d17a9f2239`). Architect proposal (Boyle, +01a1225a-9ed0) D1-D6 accepted. This doc supersedes 020 rows W3, W4, W6, W7, W8 and audit item 2's harness wording; +020's other rows and audit items 3-5 stay in force. + +## Decisions + +| ID | Decision | +|---|---| +| D1 | Harness = three `#[ignore]` tests added only to mini's temp clone `%TEMP%\ocx-probe-261010\repo` in a `#[cfg(all(test, windows))]` module of `desktop/src-tauri/src/cli_command.rs` (private fns are reachable there): `probe_bundle_program_files` calls only `windows::stable_bundle(C:\Program Files\OpenCodex\opencodex-desktop.exe, false, "2.77.0")`; `probe_plan_only` calls `windows::plan(&Record::fresh(bundle), bundle, false)` without `transact` (no write; prints would-be HKCU text and issues); `probe_install` runs `record::check(&home, true)` then `perform_selected(\.opencodex-desktop, &home, Action::Reconcile, Some(fresh record with install_id), \|\| windows::stable_bundle(%USERPROFILE%\ocx-probe-desktop\opencodex-desktop.exe, false, "2.82.0-probe"), \|_\| {})`; `probe_remove` runs `perform_selected(..., Action::Remove, None, ...)`. `` = `%TEMP%\ocx-probe-261010\home` (exists before the call). Each prints its Status as JSON plus the raw HKCU Path text/kind before and after, and its own process SessionId. The staging dir holds copies of the installed `opencodex-desktop.exe` and `ocx.exe`; neither copy is ever executed. Not exercised: Tauri scheduling and home lookup, `identity::install_id` minting, `current_exe`/`package_info`, UI state publication and the CLI page's Repair/Off/Remove, and a packaged launch (single instance). | +| D2 | `probe_install` and `probe_remove` run as the compiled test binary from `/IT` scheduled tasks in session 1 (where Explorer and the real Desktop live), never from SSH; one SSH-launched run of `probe_plan_only` is the no-write control. | +| D3 | Lanes: **W-a** (the only writer: W2 baseline, D1 tests, W4 probes before and after, remove, compare/restore) and **W-b** (read-only against the live runtime plus focused tests and a temp package copy). They share no paths, ports or registry keys. | +| D4 | Live-session limits: the HKCU window (install → remove) stays under 10 minutes; exactly two broadcasts (install, remove); restore runs even after a failure; every task has a unique logged name and is deleted by exact name; each probe window exits itself; Desktop 2.77.0 predates #6816 and cannot read the temp record. | +| D5 | F3 evidence without a write: `probe_plan_only` output (would-be text + `machine-path-conflict`) plus the observed `Get-Command ocx -All` order; W5 is labelled simulated (Machine then User PATH composition). | +| D6 | W6 is module-level removal; UI Remove stays unverified (as Linux L12). The doctor reclaim flag to avoid is `--reclaim-response-temps`. PID 22680 is re-checked at start. | + +## Lane W-a (writer) + +1. `whoami`, `query session`, `Get-Process opencodex-desktop,ocx | select Id,SessionId,Path`; save raw HKCU Path + (`(Get-Item HKCU:\Environment).GetValue('Path',$null,'DoNotExpandEnvironmentNames')`) and `GetValueKind('Path')` to + `%USERPROFILE%\ocx-probe-desktop\path-before.json` plus `reg export HKCU\Environment ...\env-before.reg /y`. +2. Add the four tests to the clone (patch file kept in the lane evidence dir), build with + `cargo test --manifest-path desktop\src-tauri\Cargo.toml --no-run`, locate the test exe; run + `probe_bundle_program_files` and `probe_plan_only` over SSH (`-- --ignored --exact --nocapture`). +3. Write `probe.cmd` (PATH evaluated at run time, `where ocx`, PowerShell dump of self/parent/grandparent name, PID, + SessionId, output to a per-run file, then exit). Baseline the four probes: /IT task `cmd /c probe.cmd`, /IT task + running `explorer.exe probe.cmd`, /IT task `wt.exe -w new new-tab --reloadEnvironment cmd /c probe.cmd`, SSH `cmd`. +4. /IT task runs the test exe `probe_install` (output to file); wait for completion; verify HKCU now starts with the + staging dir and kind unchanged; rerun the four probes. +5. /IT task runs `probe_remove`; compare raw value and kind with step 1; if only the owned entry differs, set the saved + raw value with the saved kind and broadcast; any other difference stops and is reported. +6. Delete tasks by exact name, `%USERPROFILE%\ocx-probe-desktop`, the temp home; keep the clone for W-b until both lanes end. + +Stop: Path differs from baseline before install; harness `Err`, `lock-busy`, `journal-conflict`; a probe window open +after 60 s; any restore difference beyond the owned entry. + +## Lane W-b (read-only) + +1. `Get-Process opencodex-desktop,ocx`, listening port of the sidecar; `bun install --frozen-lockfile` in a second + clone `%TEMP%\ocx-probe-261010\b` (avoid W-a's cargo tree). +2. With the real USERPROFILE: `bun src\cli\index.ts resolve --json`, `status`, `status --json` (`startupSource`, + `cliCommand`), `doctor` (no flags); `node -e` import of `src/service/desktop-supervision.mjs` → + `inspectDesktopSupervision()` result on win32 while the Desktop child is live. +3. Synthetic record diagnostics through a script calling `collectCliPathDiagnostics({home, env, recordRead})` with a + ready win32 record whose CLI is `C:\Program Files\OpenCodex\ocx.exe` (labelled synthetic). +4. Focused tests with the pinned Bun: `tests/lib/bun-runtime-preflight.test.ts`, `tests/service/service-runtime-preflight.test.ts`, + `tests/codex-integration/codex-shim-runtime-preflight.test.ts`, `tests/service/service-desktop-runtime-preflight.test.ts`, + `tests/cli/ocx-launcher-desktop-handoff.test.ts`, `tests/cli/cli-path-diagnostics.test.ts`, `tests/update/update-desktop-owner.test.ts`, + `tests/service/service-ownership-handover.test.ts`, `tests/cli/cli-stop-json.test.ts`, `tests/cli/ocx-launcher-runtime.test.ts` + with HOME/USERPROFILE/OPENCODEX_HOME/CODEX_HOME in temp. +5. W10: temp package copy, bundled Bun and installer removed, Bun 1.4.2 (`bun-windows-x64.zip`) first on PATH → + `using PATH Bun` notice; then with only the user's Bun; capture the failure text and whether it names the Desktop CLI. + +Never run against the live install: `ocx start`, `stop`, `service install/repair/start/restart/stop`, `update`, +`doctor --reclaim-response-temps`. Stop if the Desktop PID or its port changes. + +## Reflection (Boyle): ALIGNED with fixes → dispositions + +1. D1 has four ignored tests (`probe_bundle_program_files`, `probe_plan_only`, `probe_install`, `probe_remove`). +2. Before step 6, W-a copies `path-before.json`, `env-before.reg`, every probe output and the harness outputs to the lane + evidence directory (scp to the coordinator's `.tmp/evidence/mini-a/`). +3. The SSH-versus-session-1 broadcast difference is not observed (two-broadcast limit); it is recorded as unverified and + the harness prints its own SessionId. +4. Task completion: the harness and `probe.cmd` write a `done` marker file last; W-a polls for it and for + `schtasks /Query /TN /V /FO LIST` not Running, with a 120 s timeout that is a stop condition. +5. Step 4 expectations: install Status `phase=partial` with `machine-path-conflict`; `cli.json` `notifyPending=false` + after the broadcast; after `probe_remove`, `enabled=false` and `windows=null`. +6. W-b inspector import: `node --input-type=module -e "import('./src/service/desktop-supervision.mjs').then(async m=>console.log(JSON.stringify(await m.inspectDesktopSupervision())))"`. + +## Audit (independent Sol, 01a12288-5370): FAIL → dispositions + +1. **W-b doctor isolation.** `doctor` runs with `CODEX_HOME` and every other client home set to a temp directory (it + takes a native-main claim that opens SQLite under `CODEX_HOME`, `src/codex/native-main-claim.ts:86`); the real + `USERPROFILE` and `~/.opencodex` stay so it still sees the live runtime. `status`/`resolve` keep the real homes and + their port probes are recorded as observations of the live runtime. +2. **External finally.** W-a wraps steps 4-5 in a PowerShell `try { ... } finally { ... }` driver (kept in the + evidence dir) whose `finally` always: (a) waits for/kills only its own harness task by exact name; (b) runs + `probe_remove` from an /IT task if the record exists (`perform_selected` with `Action::Remove` saves off first and + `recover` rolls back a pending install journal, `cli_command.rs:179-206`); (c) compares raw Path text and kind + with `path-before.json`; (d) if only the owned staging entry differs, writes the saved raw value with the saved + kind and broadcasts once; (e) if anything else differs, leaves the value, preserves the temp record and journals, + and reports. The broadcast limit becomes "install, remove, plus at most one notification retry and one fallback + restoration broadcast". +3. **Exact test selection.** Every invocation is ` cli_command::probe_tests:: --ignored --exact + --nocapture --test-threads=1` (module `probe_tests`), and the driver requires the output line + `test result: ok. 1 passed`, the done marker, and the expected transition (HKCU starts with the staging entry after + install; raw value and kind equal the baseline after remove) before continuing. diff --git a/devlog/_fin/261010_cli_path_crossos_probe/029_wp3_outcome.md b/devlog/_fin/261010_cli_path_crossos_probe/029_wp3_outcome.md new file mode 100644 index 00000000000..ef2cde88f43 --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/029_wp3_outcome.md @@ -0,0 +1,26 @@ +# 029 — wp3 outcome: Windows on mini + +Source `d17a9f2239`. Two Sol lanes under 021: W-a (the only writer, session-1 harness) and W-b (read-only against +the live Desktop 2.77.0 runtime). Raw evidence stays in the worktree scratch `.tmp/evidence/mini-{a,b}/`. + +| Row | Result | Evidence | +|---|---|---| +| W1 Rust `cli_command` tests | **coordinator-reported, raw output unavailable** — `27 passed; 0 failed` on `x86_64-pc-windows-msvc` | recorded in 001; the raw output was not retained | +| Install eligibility | **works** — `stable_bundle(C:\Program Files\OpenCodex\opencodex-desktop.exe)` → `kind: windows-install`, CLI `C:\Program Files\OpenCodex\ocx.exe`; the per-machine MSI qualifies | mini-a `harness-bundle.txt` | +| W3 real HKCU write (module level, `perform_selected` from an /IT task in session 1) | **works** — HKCU `Path` gained the staging directory first, `REG_EXPAND_SZ` kept, persisted `notifyPending:false` after the broadcast; Status `phase: partial` with `machine-path-conflict` (see F7) | mini-a `harness-install.txt`, `state-after.json` | +| W4 new terminal pickup (release QA) | **works** — after the write, a Windows Terminal tab (`wt -w new new-tab --reloadEnvironment`, parent `WindowsTerminal.exe`, session 1), an Explorer-launched `cmd` (parent `explorer.exe`, session 1) and a Task Scheduler `cmd` all carried the staging directory and `where ocx` returned `%USERPROFILE%\ocx-probe-desktop\ocx.exe` **first**, ahead of nvm4w; before the write all three found nvm4w first. The SSH shell (session 0) stayed stale, as expected | mini-a `probe-pre-*.txt`, `probe-post-*.txt` | +| W5 machine conflict | **broken (diagnostic, F7)** — `machine_conflict()` (`cli_command_windows.rs:805`) expands Machine `Path` with the Desktop's own environment and reports `machine-path-conflict` for `%NVM_SYMLINK%` (`C:\nvm4w\nodejs\ocx.cmd`). Fresh logon environments on mini keep `%NVM_HOME%;%NVM_SYMLINK%` literal (Machine `Path` is `REG_EXPAND_SZ` and both variables exist at machine scope), so nvm4w's `ocx` only arrives through the User `Path`, after the Desktop entry. The Desktop would show "partial" while its `ocx` actually wins | mini-a `machine-path.json`, `harness-plan.txt`, `probe-post-wt.txt` | +| W6 removal (module level) | **works** — `Action::Remove` → `enabled:false`, `phase: disabled`, `windows: null`; raw `Path` text and kind equal the baseline byte for byte; HKCU window 0.18 min | mini-a `harness-remove.txt`, `cli-after-remove.json`, `state-final.json` | +| Packaged Desktop launch, Tauri home lookup, install-id minting, CLI page actions | **unverified** — single instance with the user's live 2.77.0 (`lib.rs:298`); SSH-versus-session-1 broadcast difference also unobserved | — | +| W7 handoff | **by design** — package launcher never hands off on Windows (`windows-path-only`); `status` prints `package handoff=disabled on Windows (user Path selects Desktop)`; synthetic ready record → `packageHandoff: disabled-on-windows`, `path-first-not-desktop` | mini-b `live.out`, `synthetic.out` | +| W7 status/doctor/resolve with the live Desktop | **broken (F2)** — `resolve --json` `supervisor: {"kind":"unsupported"}` and `inspectDesktopSupervision()` → `unsupported` while `Win32_Process` shows sidecar 33476 is the child of `opencodex-desktop` 22680, both under `C:\Program Files\OpenCodex`, session 1. `status` tells the user to restart the proxy with `ocx service restart` (version skew 2.82.0 vs 2.77.0), reports `Restart safety: AT RISK` and `run 'ocx service repair'`; `doctor` (isolated `CODEX_HOME`) recommends `ocx service repair` and `ocx service restart` — the advice #6802 suppresses on macOS/Linux | mini-b `live.out`, `doctor.out` | +| W8 refuse-to-compete (#6809) | **not active on Windows (F2)** — by code: `ocx stop` takes the ordinary stop path (no Desktop warning, `desktop-runtime-guidance.ts:27`), `service install` passes `desktop-command-guard.ts:24` and may clear existing managers and the tracked listener, `update` gets no Desktop veto from `runtime-ownership.mjs:45`. Not run live | mini-b report | +| W9 focused tests | **broken (tests, F6)** — 246 pass / 23 skip / 3 fail. `codex-shim-runtime-preflight.test.ts:86`, `:111` and `service-runtime-preflight.test.ts:120` expect the absolute `process.execPath` in generated `.cmd` files, but both generators intentionally render paths under the profile as `%LOCALAPPDATA%\...`/`%USERPROFILE%\...` (`src/lib/win-paths.ts`); any Windows machine whose Bun lives in the default `~\.bun\bin` fails, while CI's tool-cache Bun passes | mini-b `tests.out` | +| W10 PATH-Bun fallback | **works** — Bun 1.4.2 first on PATH: `opencodex: using PATH Bun 1.4.2.`, exit 0; user's 1.3.14 only: `the \`bun\` dependency is not installed.`, exit 1, no Desktop CLI named (F4 also holds on Windows) | mini-b `w10_run.out` | + +Host state after wp3: raw HKCU `Path` and kind equal the baseline; no `ocx-probe-261010-*` tasks; staging directory, +temp homes, lane clone `b` and W-a's source patch removed; Desktop PID 22680 and sidecar 33476 unchanged in session 1; +ports 10100/10200/52599 unchanged. The coordinator removes `%TEMP%\ocx-probe-261010` at closeout. + +Secret handling: W-a's full `reg export HKCU\Environment` contained an authentication-token value; the raw copy was +deleted on mini and locally, and only a PATH-only export is kept. diff --git a/devlog/_fin/261010_cli_path_crossos_probe/030_release_qa_matrix.md b/devlog/_fin/261010_cli_path_crossos_probe/030_release_qa_matrix.md new file mode 100644 index 00000000000..1d318d642fe --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/030_release_qa_matrix.md @@ -0,0 +1,23 @@ +# 030 — Release-QA items and the shared findings matrix + +The three release-QA items left open by `261009_desktop_owned_path_cli/049_outcome.md`, plus the matrix both probes +fill. Rows are filled in B of wp2/wp3 with the command and output tail. + +| Item | Host | Probe | Result | +|---|---|---|---| +| Fish in a real login shell | lidge | 010 L4/L6/L7 (portable fish 4.9.3 as the probe user's login shell) | works (019 L7); non-interactive fish keeps `/usr/bin/ocx` by design | +| Linux .deb path | lidge | 010 L3/L5/L6/L12 | works for build, install kind, reconcile and rc blocks (019); opt-out unverified | +| New Windows terminal after a real registry write | mini | 020 W3/W4/W6 | works (029 W4): Terminal tab with `--reloadEnvironment`, Explorer-launched and Task Scheduler `cmd` select the Desktop `ocx` first; module-level write, packaged launch unverified | +| Packaged click-through of the CLI page | — | not in this unit's reach without a GUI operator; recorded as unverified unless the Linux Xvfb run exposes the page | pending | + +## Matrix + +| Surface | Linux (lidge) | Windows (mini) | +|---|---|---| +| PATH installer (#6816) | L1, L2, L6 (packaged deb in overlay) | W1, W3 (module level; packaged launch unverified) | +| New shell / terminal pickup | L7 | W4 | +| Package launcher handoff (#6818) | L8 | W7 (by design: none) | +| status / doctor / resolve (#6802, #6818) | L9 | W7 | +| refuse-to-compete guards (#6809) | L10 | W8 | +| Bun fallback (#6807) / preflight (#6812) | L11 | W9, W10 | +| Opt-out cleanup | L12 | W6 | diff --git a/devlog/_fin/261010_cli_path_crossos_probe/040_fixes_issues_closeout.md b/devlog/_fin/261010_cli_path_crossos_probe/040_fixes_issues_closeout.md new file mode 100644 index 00000000000..14fd4259949 --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/040_fixes_issues_closeout.md @@ -0,0 +1,43 @@ +# 040 — wp4: fixes, issues and closeout + +Each defect found in wp2/wp3 becomes a PR to `dev` (focused tests on the host where it reproduces, exact-head CI, +independent Sol review) or an issue when it needs a design decision. PR branches are cut from `origin/dev` in +`.tmp/lanes/` inside this worktree. PRs follow `.github/PULL_REQUEST_TEMPLATE.md`; issues use the matching +`.github/ISSUE_TEMPLATE/*.yml` headings. + +## Known before the probes + +| ID | Finding | Evidence | Disposition | +|---|---|---|---| +| F1 | `cli_command_record::tests::final_save_before_journal_deletion_is_cleaned_on_reopen` failed once on Linux with `lock-busy` (1 of 4 full `cli_command` runs on lidge; isolated 5/5 pass). `lock()` (`cli_command_record.rs:536`) takes `flock(LOCK_EX\|LOCK_NB)` once. Hypothesis at the time (never confirmed): a child forked by a parallel test briefly shares the lock's open file description until exec, so a drop-then-reopen sees the lock still held. | lidge cargo log | Superseded after wp2: not reproduced in 60 runs and no lock holder captured, so no PR and no issue (see "After wp2") | +| F2 | Windows has no Desktop-supervision detection: #6802 authority, #6809 refusals and #6818 handoff never engage, so a package `ocx stop`/`service install`/`update` can act against a Desktop-owned runtime | 020 W7/W8 | Issue (feature proposal): Windows process-identity proof for the Desktop child (parent PID + image path via `QueryFullProcessImageNameW`/toolhelp snapshot), equivalent to the Linux `/proc` checks | +| F3 | Machine Path npm (nvm4w, Program Files nodejs) always beats the HKCU entry; Desktop reports `partial` but the npm `ocx` stays selected and, without a handoff, can be far older than the Desktop | 020 W3/W4 | Issue or docs PR depending on W4 evidence | + +Further rows are appended from the probe results. Closeout records PR/issue links, head SHAs, CI run ids, review +verdicts and the host revert table. + +## Reflection dispositions + +- F1 stays a hypothesis until wp2 captures the lock holder (010 dispositions); the PR follows that evidence. +- F2 is about supervision only; the Windows handoff being PATH-only is a separate, deliberate design choice and is + described as such. +- F4 (candidate): on Linux, a package launcher that cannot run names no Desktop CLI because `findDesktopCli` is + macOS-only (`src/lib/bun-path-runtime.mjs:79`), even though the deb ships `/usr/bin/ocx`. + +## After wp2 + +- F1: not reproduced in 60 runs; no PR. Mentioned in the closeout as an unexplained single failure. +- F4: confirmed (`Bun binary missing after install attempt.`); follow-up rather than a fix, see 019. +- F5 (new, fixable): `real_shell_selects_desktop_shim_on_temp_home` requires exactly two stdout lines from + `bash -l -i`, which fails on stock Ubuntu for any sudo-group user because `/etc/bash.bashrc` prints the sudo hint. + PR: emit sentinel-prefixed lines (`printf 'OCX-PATH:%s\nOCX-OUT:%s\n' ...`) and assert on those lines only. + +## After wp3 + +| ID | Disposition | +|---|---| +| F2 | Confirmed live (029 W7/W8). Issue (feature proposal): Windows Desktop-supervision detection from `Win32_Process`-equivalent facts (parent PID, both image paths under the install directory, session, creation time, two agreeing snapshots), so #6802 authority and #6809 refusals apply; PATH-only handoff stays a separate decision. | +| F3 | Superseded by F7: on mini the Desktop `ocx` does win in new terminals. | +| F4 | Holds on Linux and Windows; folded into the F2 issue as a related follow-up (name the Desktop CLI in launcher failure text off macOS). | +| F6 | PR: compute the expected rendered path with `windowsEnvIndirectBatchValue` in the three assertions. | +| F7 | Issue (bug): `machine-path-conflict` false positive; evaluate Machine `Path` as a fresh logon environment does (e.g. `CreateEnvironmentBlock` with `bInherit = FALSE`) instead of `ExpandEnvironmentStringsW` in the Desktop process. | diff --git a/devlog/_fin/261010_cli_path_crossos_probe/041_wp4_execution.md b/devlog/_fin/261010_cli_path_crossos_probe/041_wp4_execution.md new file mode 100644 index 00000000000..4f621db922d --- /dev/null +++ b/devlog/_fin/261010_cli_path_crossos_probe/041_wp4_execution.md @@ -0,0 +1,127 @@ +# 041 — wp4 execution: two fix PRs, two issues, closeout + +Stale check: `origin/dev` 93ed1a40b4; none of the files below changed since `d17a9f2239`. Neither test file is in +`tests/fixtures/file-size-baseline.json`. `gh` is unauthenticated and `git push` has no credential on this Mac, so +branches, commits and PRs are created through the GitHub connector (blob → tree → commit on the `dev` head → +`create_branch` at that commit → `create_pull_request` to `dev`), from local commits made in lane worktrees. + +## PR 1 — F5: real-shell evidence test survives system rc output + +Lane worktree `.tmp/lanes/f5-real-shell-markers` (inside this worktree), branch `codex/real-shell-test-markers` from +`origin/dev`. MODIFY `desktop/src-tauri/src/cli_command_posix.rs` in `real_shell_selects_desktop_shim_on_temp_home`: + +```diff +- let output = command +- .args(["-i", "-c", "command -v ocx; ocx hello"]) ++ // System rc files may print (Ubuntu's /etc/bash.bashrc sudo hint); only marked lines count. ++ let output = command ++ .args([ ++ "-i", ++ "-c", ++ "printf 'OCX-PATH:%s\\n' \"$(command -v ocx)\"; printf 'OCX-OUT:%s\\n' \"$(ocx hello)\"", ++ ]) + .output() + .expect("cannot start real shell"); + assert!(output.status.success(), "{shell}: command failed"); + let stdout = String::from_utf8(output.stdout).expect("shell stdout is not UTF-8"); +- let lines: Vec<_> = stdout.lines().collect(); ++ let marked = |tag: &str| stdout.lines().filter_map(|l| l.strip_prefix(tag)).collect::>(); ++ let (paths, outputs) = (marked("OCX-PATH:"), marked("OCX-OUT:")); + // Keep captured system-rc output and temporary HOME paths out of failure logs. + assert!( +- lines.len() == 2, +- "{shell}: expected exactly two stdout lines" ++ paths.len() == 1 && outputs.len() == 1, ++ "{shell}: expected exactly one marked path and one marked output line" + ); + assert!( +- lines[0] == expected_path.to_str().unwrap(), ++ paths[0] == expected_path.to_str().unwrap(), + "{shell}: command resolution selected the wrong executable" + ); +- assert!(lines[1] == expected_output, "{shell}: wrong CLI output"); ++ assert!(outputs[0] == expected_output, "{shell}: wrong CLI output"); +``` + +(final layout per `cargo fmt`). Verification: on lidge (Ubuntu, sudo-group user, the failing case) and on this Mac +(zsh + bash), `cargo test --manifest-path desktop/src-tauri/Cargo.toml cli_command_posix::tests::real_shell_selects_desktop_shim_on_temp_home -- --ignored --exact --nocapture` +must pass (lidge failed before with `expected exactly two stdout lines`); `cargo fmt --check` and +`cargo clippy --all-targets -- -D warnings` for the desktop crate (CI stubs as in ci.yml:1567-1573); plain +`cargo test ... cli_command` stays green. Activation: lidge's `/etc/bash.bashrc` sudo hint is the scenario; the +test only passes there if the extra stdout line is ignored. + +## PR 2 — F6: Windows preflight assertions follow the env-indirect path rendering + +Lane worktree `.tmp/lanes/f6-preflight-test-paths`, branch `codex/preflight-tests-env-indirect-paths`. + +- MODIFY `tests/codex-integration/codex-shim-runtime-preflight.test.ts`: import + `windowsEnvIndirectBatchValue` from `../../src/lib/win-paths`; add + `const renderedRuntimePath = () => windowsEnvIndirectBatchValue(runtime.path, value => value);` with a one-line + comment (wrappers write profile paths as `%LOCALAPPDATA%`/`%USERPROFILE%` tokens, `src/lib/win-paths.ts`); lines 86 + and 111 change `toContain(runtime.path)` → `toContain(renderedRuntimePath())`. +- MODIFY `tests/service/service-runtime-preflight.test.ts`: same import; line 120 + `toContain(runtime.path)` → `toContain(windowsEnvIndirectBatchValue(runtime.path, value => value))`. + +The rendered value equals `runtime.path` when no indirection variable prefixes it (macOS, Linux, CI's tool-cache Bun), +so the assertions keep their meaning there and become correct for a Bun under the profile. The identity escape is +enough because `process.execPath` contains none of `% ^ "`; a comment says so. + +Verification: on mini (Windows, Bun under `%USERPROFILE%\.bun` — the failing case), `bun test +tests/codex-integration/codex-shim-runtime-preflight.test.ts tests/service/service-runtime-preflight.test.ts` with +isolated homes → 0 fail (was 3 fail); same two files on this Mac → 0 fail; `bun run typecheck`. + +## Issues + +| Issue | Template | Content | +|---|---|---| +| I1 Windows Desktop supervision (F2, F4) | `feature_request.yml` headings (Area, What are you trying to accomplish?, What prevents this today?, What should OpenCodex do?, Example usage or interface, Alternatives or workarounds, Additional context, Checks) | 029 W7/W8 evidence: `supervisor: unsupported` with the Win32 parent/child facts, status/doctor service advice, which #6802/#6809 paths stay inert; proposal: Windows process-identity proof (parent PID, both image paths in the install directory, session, creation time, two agreeing snapshots) feeding `inspectDesktopSupervision`; F4 as a related follow-up (name the Desktop CLI in launcher failure text off macOS: Windows `InstallLocation`, Linux deb `/usr/bin/ocx` only when it is the package's ELF) | +| I2 `machine-path-conflict` false positive (F7) | `bug_report.yml` headings (Client or integration, Area, Summary, Reproduction, Version, Operating system, Provider and model, Logs or error output, Screenshots and supporting files, Redacted configuration, Checks) | 029 W3/W4/W5 evidence; proposed direction: read `PATH` from `CreateEnvironmentBlock(token, NULL, FALSE)` and test whether any entry before the Desktop entry holds `ocx.{exe,com,cmd,bat}` | + +Issue bodies are drafted in scratch (`.tmp/issues/`), contain no account data, and are created with the connector. + +## Closeout (wp4 C/D) + +1. Independent Sol review of both PR diffs (exact head) and of 019/029/040; record the verdict and rounds. +2. Exact-head CI for both PRs observed through the connector; Windows shards do not run on ordinary PR CI, so mini's + run is the Windows evidence for PR 2 (stated in the PR). +3. Remove mini's `%TEMP%\ocx-probe-261010` and any lidge leftovers; final host table in 049. +4. 049_outcome.md: findings matrix summary, PR/issue links, heads, CI runs, reverts. The devlog unit itself is + published as a third, docs-only PR from this branch (`codex/cli-path-crossos-probe`). + +## Architect consultation (Sol 01a1229a-dfe4, proposal written before reading this doc) + +| ID | Proposal | Disposition | +|---|---|---| +| P1 | Sentinel-prefixed result lines; capture each command and keep its failure status; shared bash/zsh syntax; reject `.hushlogin` (changes the tested environment) and stderr redirection (the hint is on stdout) | accepted; the `-c` script becomes `p=$(command -v ocx) \|\| exit 11; o=$(ocx hello) \|\| exit 12; printf 'OCX-PATH:%s\\nOCX-OUT:%s:OCX-END\\n' "$p" "$o"`, so a failing lookup or CLI still fails `status.success()`; final #6851 head `d785fb1b02` also requires `outputs[0] == format!("{expected_output}:OCX-END")`, so an extra CLI output line fails the comparison | +| P2 | Expected value from `windowsEnvIndirectBatchValue`; assert the complete `set "OCX_BUN=..."` line; keep the frozen-runtime and `unprobed.exe` checks | accepted; the three assertions check `set "OCX_BUN="` (the shim and the service script both emit that line), using the test-local `batchValue` escape for the literal suffix (see audit item 1) | +| P3a | Feature issue for Windows supervision with the observed trigger, the proof shape, `unknown` on inconclusive evidence; PATH-only handoff stays separate; F4 related, Linux needs deb-vs-npm-symlink distinction | accepted as I1 | +| P3b | Bug issue for F7; desired behaviour is fresh-logon PATH evaluation; validate `CreateEnvironmentBlock(FALSE)` against real new terminals before choosing it; no speculative PR | accepted as I2 (wording: candidate direction, to be validated) | +| P4 | No issue for F1; keep the observation and capture the lock holder on recurrence | accepted | + +Reflection: the proposal and this plan match on every decision; the only amendments are the two concrete P1/P2 forms +above. + +## Audit (independent Sol, 01a1229c-7b16): NEAR-PASS → dispositions + +1. PR2 expected values use a test-local `batchValue` that applies the generators' own suffix escaping + (`%` → `%%`, `^` → `^^`, drop `"`; `src/codex/shim-templates.ts:170`, `src/service/windows-taskxml.ts:14`) through + `windowsEnvIndirectBatchValue(runtime.path, batchValue)`, and assert the complete `set "OCX_BUN=..."` line. The + frozen-runtime and `unprobed.exe` checks stay. +2. Issues use `### ` sections in form order, with the dropdown value or text under each, and the form's + labels (`bug` / `enhancement`). The forms define no `title:` prefix; titles follow the repository convention + (`[Bug] ...` / `[Feature]: ...`). `enforce-issue-quality` detects the kind from content and validates the body + (`.github/scripts/issue-quality-core.cjs` `detectIssueKind`, `validateIssueBody`), not the submission route. Each + PR body fills Summary, Verification and Checklist from `.github/PULL_REQUEST_TEMPLATE.md` with the host evidence. +3. Closeout step 1 records the actual review verdict and its round count rather than presupposing PASS; PR1's Rust + literal keeps `\\n` and plain `||`. + +## Audit round 2 (01a1229c-7b16): NEAR-PASS, one residual → rebuttal + +The forms define no title prefix; the bug form adds the `bug` label and the feature form `enhancement`. Titles follow +the repository's existing convention (`[Bug] ...`, `[Feature]: ...`, e.g. #6720, #3191). + +Residual: AGENTS.md asks agents to open issues through the template chooser. Issue forms can only be submitted through +the web UI and `gh` is unauthenticated here, so the issues are created through the GitHub connector with the body the +form would render (same `###