diff --git a/.github/workflows/cef-learning-harness.yml b/.github/workflows/cef-learning-harness.yml index 8b432c87..100a5c4f 100644 --- a/.github/workflows/cef-learning-harness.yml +++ b/.github/workflows/cef-learning-harness.yml @@ -118,11 +118,26 @@ jobs: "http://localhost:8080/" --cycles 3 kill "$SERVER_PID" + - name: Best-effort Wayland launch smoke (roadmap Β§44.2) + id: wayland-smoke + continue-on-error: true + run: | + sudo apt-get install -y weston + python3 -m http.server 8081 --directory dist & + SERVER_PID=$! + sleep 1 + node scripts/cef/run-wayland-smoke.mjs \ + "$(pwd)/build/worldscript_host/worldscript_host" \ + "http://localhost:8081/" + kill "$SERVER_PID" + - name: Summary + if: always() run: | echo "## πŸ§ͺ CEF Learning Harness" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "- Pinned SDK: \`$(node -e "console.log(require('./scripts/cef/cef-version.json').cefVersion)")\`" >> "$GITHUB_STEP_SUMMARY" echo "- Cache hit: \`${{ steps.cef-cache.outputs.cache-hit }}\`" >> "$GITHUB_STEP_SUMMARY" echo "- worldscript_host built and repeated launch/close cycles proven against the real production bundle (dist/), under Xvfb." >> "$GITHUB_STEP_SUMMARY" - echo "- Not yet in scope: X11/Wayland matrix beyond this one runner, sandbox posture, accessibility smoke, crash-reporting proof." >> "$GITHUB_STEP_SUMMARY" + echo "- Wayland smoke (best-effort, roadmap Β§44.2): \`${{ steps.wayland-smoke.outcome }}\`" >> "$GITHUB_STEP_SUMMARY" + echo "- Not yet in scope: X11/Wayland matrix beyond this one runner, sandbox posture, accessibility smoke." >> "$GITHUB_STEP_SUMMARY" diff --git a/docs/architecture/native-readiness.md b/docs/architecture/native-readiness.md index 47e6f343..564b9ef3 100644 --- a/docs/architecture/native-readiness.md +++ b/docs/architecture/native-readiness.md @@ -57,7 +57,8 @@ Wave 2's first deliverable β€” the CEF binding/C++ decision β€” is now backed by | Check | Result | Owner | Notes | |---|---|---|---| | CEF binding/integration approach decided | **PASS** | cef-runtime | Option B (thin C++ CEF host + Rust core) chosen and spiked with real evidence: builds against CEF's own binary-distribution CMake macros, launches under Xvfb, survives 3 repeated start/close cycles with a clean process tree, and its core premise (a working Rust↔C++ FFI boundary) is proven in isolation. See ADR-0020. | -| CEF renders reliably on Linux dev systems | DEBT β€” partial | cef-runtime, Wave 2 (in progress) | Two data points now (dev-machine spike + CI-run PR #388 on GitHub Actions `ubuntu-latest`), the second of which renders the **real production bundle** (not `about:blank`) with a verified exact title, not just a blank page. Still one GPU config per machine, no sandbox, X11/Xvfb only. Exit condition (unchanged from roadmap Β§2's Wave 2 exit criteria): broader Linux/GPU/display-server matrix coverage (Appendix A.3) before this can flip to PASS. | +| CEF renders reliably on Linux dev systems | DEBT β€” partial | cef-runtime, Wave 2 (in progress) | Two data points now (dev-machine spike + CI-run PR #388 on GitHub Actions `ubuntu-latest`), the second of which renders the **real production bundle** (not `about:blank`) with a verified exact title, not just a blank page. Still one GPU config per machine, no sandbox, mainly X11/Xvfb (Wayland now also smoke-proven, see the dedicated row below). Exit condition (unchanged from roadmap Β§2's Wave 2 exit criteria): broader Linux/GPU/display-server matrix coverage (Appendix A.3) before this can flip to PASS. | +| Wayland display-server smoke | **PASS** β€” single-runner smoke only | cef-runtime, Wave 2 | PR #393: `worldscript_host` (the exact binary already proven under X11) also renders the real production bundle under a headless Weston Wayland compositor (`--ozone-platform=wayland`), same FFI-boundary + exact-title checks as the X11 harness, CI-run and non-blocking (roadmap Β§44.2). Grounded in real evidence before attempting: Chromium's own upstream GN default compiles Wayland Ozone support into every standard Linux build, and CEF's `tools/gn_args.py` has no override disabling it. Does **not** satisfy roadmap Β§44.2/Β§44.5's real-hardware/compositor matrix (NVIDIA/AMD/Intel Γ— KDE/GNOME) β€” one virtual CI runner, one compositor implementation (Weston headless), no real GPU. | | CEF lifecycle assumptions documented | **PASS** | cef-runtime | `docs/cef/knowledge/subprocess-and-shutdown.md`'s core Wave 2 claim (SIGTERM β†’ graceful `TryCloseBrowser`/`OnBeforeClose`/`CefQuitMessageLoop`/`CefShutdown`, repeated clean start/close cycles) now has a real linked chain: test (`scripts/cef/run-launch-cycle-proof.mjs`) β†’ CI job (`πŸ§ͺ CEF Learning Harness`) β†’ doc, exactly what Β§61.1.4 requires. Save-coordinator/window-state persistence remain explicitly Wave 5+ scope (not a Wave 2 gap); Windows/macOS and a real packaged layout remain open, tracked in the doc's own "Outline" section. | | Early Accessibility Gate | Not yet attempted (real blocker found) | cef-runtime, Wave 2 | PR #391 attempted `CefAccessibilityHandler` β€” does not compile against CEF 151.3.18 (`CefClient::GetAccessibilityHandler()` doesn't exist in this version). Reverted rather than left half-working, after a fallback attempt (enable-only, no observability) regressed the previously-reliable FFI/rendering proofs. Real CEF-151 API research needed before the next attempt β€” see `docs/cef/knowledge/cef-architecture-primer.md`. | | Sandbox posture | Not yet attempted | desktop-security, Wave 2/3 (roadmap Β§12) | Every run so far used `no_sandbox=true`; zero evidence either way on this row. | @@ -67,4 +68,4 @@ Wave 2's first deliverable β€” the CEF binding/C++ decision β€” is now backed by | CEF host build + repeated launch/close cycle proof, in CI | **PASS** | cef-runtime | PR #388: `apps/desktop-cef/`'s `worldscript_host` (real, repo-committed C++/Rust source, not spike code) builds against the fetched CEF SDK and runs 3 independently-verified clean start/close cycles under Xvfb in CI β€” the roadmap's literal "isolated learning harness" / "safe repeated startup/shutdown" deliverables (Β§3142), not just the fetch/diagnostics increment. | | Rust FFI boundary proven inside the real host | **PASS** | cef-runtime, rust-core | `worldscript_rust_ping()` (rust-core, linked via Corrosion) is called from `OnAfterCreated` on every cycle and its exact sentinel value observed in CI output β€” stronger than the ADR-0020 spike's decoupled isolation test, since this proves the boundary works inside the actual multi-process CEF host, not a standalone C++ program. | -**Overall for this snapshot**: 6 PASS (one β€” crash reporting β€” explicitly PASS for its reporting half only, not symbolization), 2 explicit DEBT-in-progress rows (each with a concrete exit condition, not open-ended), 2 not-yet-attempted rows correctly left blank rather than assumed. No row is marked PASS without the evidence cited above. +**Overall for this snapshot**: 7 PASS (crash reporting explicitly PASS for its reporting half only, not symbolization; Wayland explicitly PASS for a single-runner smoke only, not the real-hardware/compositor matrix), 2 explicit DEBT-in-progress rows (each with a concrete exit condition, not open-ended), 2 not-yet-attempted rows correctly left blank rather than assumed. No row is marked PASS without the evidence cited above. diff --git a/docs/cef/CEF-RUST-COMPETENCY-MATRIX.md b/docs/cef/CEF-RUST-COMPETENCY-MATRIX.md index f918d252..d936c0f9 100644 --- a/docs/cef/CEF-RUST-COMPETENCY-MATRIX.md +++ b/docs/cef/CEF-RUST-COMPETENCY-MATRIX.md @@ -1,7 +1,7 @@ # CEF/Rust Competency Matrix **Companion to:** [`ROADMAP-CEF-DESKTOP-MIGRATION.md`](ROADMAP-CEF-DESKTOP-MIGRATION.md) Β§4.11, Β§61.1, Appendix A.1 Β· [ADR-0019](../adr/0019-cef-desktop-runtime-strategy.md) -**Established:** Wave 0, 2026-08-18. **Baseline was: nothing done yet.** Updated in place, 2026-08-18/19 (Wave 2, ADR-0020 spike + PR #386/#387/#388/#391/#392), per this doc's own "Update discipline" below β€” items flip to `true` only with a linked evidence commit, in the same commit as the flip. This file exists so future waves have a live, gradeable target instead of re-deriving the checklist from the roadmap prose each time. +**Established:** Wave 0, 2026-08-18. **Baseline was: nothing done yet.** Updated in place, 2026-08-18/19 (Wave 2, ADR-0020 spike + PR #386/#387/#388/#391/#392/#393), per this doc's own "Update discipline" below β€” items flip to `true` only with a linked evidence commit, in the same commit as the flip. This file exists so future waves have a live, gradeable target instead of re-deriving the checklist from the roadmap prose each time. This is an engineering gate (roadmap Β§4.11.6), not a training checklist. `WS-CEF-IPC` (Wave 4) and any production storage capability exposing privileged native operations may not proceed until the relevant items below are `true` with linked evidence. @@ -45,7 +45,7 @@ CI validation of this block ("fail CI when a required item for the active progra [ ] Accessibility smoke green (attempted, real blocker β€” see cef-architecture-primer.md's "Accessibility API" section) [ ] Crash-reporting/symbolization smoke green (crash-reporting half proven β€” PR #392, real Crashpad dump produced in CI; symbolization/decoding the dump not attempted, needs a full Chromium source checkout β€” see cef-architecture-primer.md) [ ] Linux dependency inventory complete (inventoried, not yet proven sufficient β€” see native-readiness.md) -[ ] X11/Wayland initial smoke complete (X11 only; Wayland zero evidence) +[x] X11/Wayland initial smoke complete β€” PR #393: X11 proven since PR #388 (Xvfb); Wayland now also proven (headless Weston compositor, --ozone-platform=wayland, same FFI+title checks, cef-learning-harness CI job). Real-hardware/compositor matrix (roadmap Β§44.2/Β§44.5 β€” NVIDIA/AMD/Intel Γ— KDE/GNOME, real graphics hardware) remains unproven; this is one virtual-CI runner only. [ ] Upgrade playbook written [ ] External-expertise escalation path documented ``` diff --git a/docs/cef/OWNERSHIP.yaml b/docs/cef/OWNERSHIP.yaml index a922bdd9..269ce2e9 100644 --- a/docs/cef/OWNERSHIP.yaml +++ b/docs/cef/OWNERSHIP.yaml @@ -56,7 +56,7 @@ documents: - cef-learning-harness # cef-competency-gate: no such CI workflow/job exists yet β€” planned, not implemented (CodeRabbit review finding on PR #389). Re-add once it's a real job. driftCheckTool: "planned β€” not implemented, see Wave 1" - note: "Updated in place for Wave 2 (PR #386/#387/#388/#391/#392) β€” 3 of 7 cef_competency items now true with linked evidence (renderer_crash_ci added, PR #392); competency gate still not satisfied (6/12)." + note: "Updated in place for Wave 2 (PR #386/#387/#388/#391/#392/#393) β€” 3 of 7 cef_competency items now true with linked evidence (renderer_crash_ci added, PR #392); Wayland smoke checked in Appendix A.1 (PR #393, no dedicated cef_competency field for it per the roadmap's own Β§61.1.3 shape); competency gate still not satisfied (6/12)." - path: docs/cef/TAURI-COUPLING-INVENTORY.md tier: B @@ -104,7 +104,7 @@ documents: related_ci: - cef-learning-harness driftCheckTool: "planned β€” not implemented, see Wave 1" - note: "Real evidence from PR #388 for process model, message loop, subprocess packaging; crash reporting proven in CI (PR #392). Sandbox config, symbolization, and a directly-observed process-tree snapshot remain open." + note: "Real evidence from PR #388 for process model, message loop, subprocess packaging; crash reporting proven in CI (PR #392); Wayland display-server smoke proven in CI (PR #393). Sandbox config, symbolization, a real GPU/compositor matrix, and a directly-observed process-tree snapshot remain open." - path: docs/cef/knowledge/cef-rust-binding-cookbook.md tier: A diff --git a/docs/cef/knowledge/cef-architecture-primer.md b/docs/cef/knowledge/cef-architecture-primer.md index 4f9ae402..91e993fe 100644 --- a/docs/cef/knowledge/cef-architecture-primer.md +++ b/docs/cef/knowledge/cef-architecture-primer.md @@ -1,6 +1,6 @@ # CEF Architecture Primer -**Status:** Real evidence from `apps/desktop-cef/` (PR #388) for process model, message loop, and subprocess packaging; crash reporting and renderer-crash resilience proven in CI (PR #392). Sandbox configuration, dump symbolization, and a directly-observed full process-tree snapshot remain open. +**Status:** Real evidence from `apps/desktop-cef/` (PR #388) for process model, message loop, and subprocess packaging; crash reporting and renderer-crash resilience proven in CI (PR #392); Wayland display-server smoke also proven in CI (PR #393), alongside X11. Sandbox configuration, dump symbolization, a real GPU/compositor matrix, and a directly-observed full process-tree snapshot remain open. **Scope:** How CEF's multi-process architecture (browser process, renderer process, GPU/utility processes; browser/frame/client ownership; message-loop integration; subprocess launch and packaging; sandbox model) maps onto WorldScript Studio's specific host and build, written from our actual integration β€” not a generic CEF tutorial. **Tier:** A (release/security-critical) β€” see [`../OWNERSHIP.yaml`](../OWNERSHIP.yaml). **Roadmap context:** [`../ROADMAP-CEF-DESKTOP-MIGRATION.md`](../ROADMAP-CEF-DESKTOP-MIGRATION.md) Β§4.11.1 ("CEF architecture" domain), Β§4.11.2, Wave 2. @@ -49,6 +49,16 @@ Unlike the accessibility attempt above, every mechanism here was verified agains **What this does NOT prove**: symbolization β€” decoding the `.dmp` file into a human-readable stack trace β€” needs `dump_syms` and `minidump_stackwalk`, which CEF's own docs say must be built from a *complete Chromium source checkout* (`gn`/`ninja`, hours of build time, tens of GB of disk). That is out of reach of this project's minimal-CEF-SDK-only CI setup (and of the local dev machine's own constrained RAM/disk, per this repo's own low-end-hardware guidance) and was not attempted. `crash_symbolization_smoke` in `docs/cef/CEF-RUST-COMPETENCY-MATRIX.md` stays `false` for that reason β€” the crash-*reporting* half is proven; symbolization is a separate, still-open item. +## Display server β€” X11 proven since PR #388, Wayland now also proven + +Roadmap Β§44.2 is explicit: *"'CEF uses Chromium' is not accepted as proof of Wayland/X11 correctness."* Until PR #393 this host had only ever been exercised under X11 (`xvfb-run`). + +**Real evidence gathered before attempting anything**, matching the discipline the accessibility attempt's own "what this means for the next attempt" note called for: Chromium's own upstream GN default (`build/config/ozone.gni`, the `is_linux` branch) compiles **both** the `x11` and `wayland` Ozone platforms into every standard Linux build (`ozone_platform_wayland = true`), and CEF's own `tools/gn_args.py` has zero ozone/wayland overrides β€” confirmed by reading both files directly. `--ozone-platform=wayland` is a real, verified Chromium switch (`ui/ozone/public/ozone_switches.cc`'s `kOzonePlatform`). + +**Directly observed evidence, PR #393**: `scripts/cef/run-wayland-smoke.mjs` launches a headless Weston compositor (`weston --backend=headless-backend.so` β€” the Wayland-side equivalent of Xvfb, no real display/GPU needed) and the *exact same already-built* `worldscript_host` binary under `--ozone-platform=wayland`, `WAYLAND_DISPLAY` pointed at Weston's socket. The CI log shows the compositor socket created, then `worldscript_host` reaching both `rust_core ping = 424242` and `title = WorldScript Studio` within about 1.2 seconds β€” the same two proofs the X11 harness uses, now also true under Wayland, on a stock GitHub Actions runner, first attempt. + +**What this does NOT prove**: roadmap Β§44.2/Β§44.5's real matrix β€” NVIDIA/AMD/Intel GPUs Γ— KDE/GNOME compositors Γ— real graphics hardware. This is one virtual CI runner, one compositor implementation (Weston, headless, no GPU), non-blocking (`continue-on-error`) in CI. It answers "does the fetched CEF binary distribution and this host even support Wayland at all" (yes), not "does WorldScript Studio work correctly under every real-world Wayland desktop" (unproven). + ## Sandbox configuration, as shipped `chrome-sandbox` is present in the output directory (copied automatically as part of `CEF_BINARY_FILES`) but is **not used** β€” `main.cpp` sets `CefSettings.no_sandbox = true` unconditionally. Zero evidence exists on real sandbox posture; this is explicitly tracked as "Not yet attempted" in `docs/architecture/native-readiness.md` and `false` in the competency manifest. diff --git a/scripts/cef/run-wayland-smoke.mjs b/scripts/cef/run-wayland-smoke.mjs new file mode 100644 index 00000000..6a8bcbb8 --- /dev/null +++ b/scripts/cef/run-wayland-smoke.mjs @@ -0,0 +1,202 @@ +#!/usr/bin/env node +/** + * Best-effort Wayland launch smoke test for the Wave 2 CEF host (roadmap Β§44.2: + * "'CEF uses Chromium' is not accepted as proof of Wayland/X11 correctness" β€” the + * mandatory run-launch-cycle-proof.mjs only ever runs worldscript_host under Xvfb/X11). + * + * This is a genuine feasibility attempt, not a guess: Chromium's own upstream GN + * default (build/config/ozone.gni, `is_linux` branch) compiles BOTH the x11 and + * wayland Ozone platforms into every standard Linux build (`ozone_platform_wayland = + * true`), and CEF's own tools/gn_args.py has no override disabling it β€” confirmed by + * reading both files directly, not assumed. What's still genuinely unverified before + * this script runs is whether the fetched "linux64 minimal" binary distribution + * actually carries that support through, and whether a headless Weston compositor + * (the Wayland-side equivalent of Xvfb β€” no real display/GPU needed) is a viable + * launch target for it on a stock GitHub Actions runner. + * + * Deliberately does NOT touch worldscript_host's build or the already-proven X11 + * proofs β€” this launches the exact same already-built binary, just under + * --ozone-platform=wayland against a headless Weston socket instead of Xvfb. The + * calling CI step is marked continue-on-error so a real "Wayland doesn't work with + * this CEF distribution/runner" finding is informative, not a regression gate. + * + * Run: node scripts/cef/run-wayland-smoke.mjs + */ +import { execFileSync, spawn } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +const [binaryPath, url] = process.argv.slice(2); + +if (!binaryPath || !url) { + console.error( + '[wayland-smoke] Usage: node scripts/cef/run-wayland-smoke.mjs ', + ); + process.exit(1); +} + +// QNBS-v3: same grace period rationale as run-launch-cycle-proof.mjs's STARTUP_GRACE_MS β€” CI-runner-speed variance, not a code concern this script has any control over. +const LAUNCH_GRACE_MS = 10000; +const COMPOSITOR_SOCKET_GRACE_MS = 5000; +// QNBS-v3: a process created in the gap between the first pgrep snapshot and its SIGKILL (or one still exiting) wouldn't be caught by a single one-shot sweep β€” CodeAnt review finding on PR #393. This is how long the second, verifying pass waits before re-sweeping. +const CLEANUP_RECHECK_GRACE_MS = 2000; +const FFI_PROOF_LINE = 'rust_core ping = 424242'; +const EXPECTED_TITLE_LINE = 'title = WorldScript Studio'; +const WAYLAND_SOCKET_NAME = 'wayland-smoke-0'; + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// QNBS-v3: shared by the two cleanup passes below β€” CEF re-execs the same binary for renderer/GPU/crashpad-handler subprocesses, none of which carry WAYLAND_SOCKET_NAME in their own command line (it's passed via env var, not argv), so a name-based pkill never matched them β€” CodeAnt review finding on PR #393. Same pattern run-launch-cycle-proof.mjs uses for the identical problem. +function sweepMatchingProcesses() { + try { + const out = execFileSync('pgrep', ['-f', `^${binaryPath}`], { + stdio: ['ignore', 'pipe', 'ignore'], + }) + .toString() + .trim(); + const pids = out.split('\n').filter(Boolean).map(Number); + for (const pid of pids) { + if (pid === process.pid) continue; + try { + process.kill(pid, 'SIGKILL'); + } catch { + // Already gone β€” fine. + } + } + return pids.filter((pid) => pid !== process.pid).length > 0; + } catch { + return false; // pgrep exits 1 when nothing matches β€” nothing to kill. + } +} + +// QNBS-v3: trusts an already-set XDG_RUNTIME_DIR as-is (GitHub Actions runners provide a real one, e.g. /run/user/) rather than chmod'ing a directory this script doesn't own β€” CodeRabbit review finding on PR #393. Only falls back to creating (and owning) its own via mkdtempSync, never a fixed predictable /tmp path. +function ensureXdgRuntimeDir() { + const existing = process.env.XDG_RUNTIME_DIR; + if (existing && fs.existsSync(existing)) { + return existing; + } + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'wayland-smoke-xdg-runtime-')); + fs.chmodSync(dir, 0o700); + return dir; +} + +async function main() { + const xdgRuntimeDir = ensureXdgRuntimeDir(); + const env = { ...process.env, XDG_RUNTIME_DIR: xdgRuntimeDir }; + + // QNBS-v3: a stale socket left by a previous crashed/killed run would otherwise satisfy the existsSync check below immediately, without this Weston instance ever actually starting β€” CodeAnt review finding on PR #393. Removing it first means "socket exists" can only mean "this Weston instance created it". + const socketPath = path.join(xdgRuntimeDir, WAYLAND_SOCKET_NAME); + try { + fs.unlinkSync(socketPath); + } catch { + // Nothing there β€” the expected common case. + } + + console.log('[wayland-smoke] Starting headless Weston compositor…'); + // QNBS-v3: Weston's headless backend needs no real display/GPU β€” the Wayland-side equivalent of Xvfb, same reasoning as run-launch-cycle-proof.mjs uses xvfb-run for X11. + const weston = spawn( + 'weston', + ['--backend=headless-backend.so', `--socket=${WAYLAND_SOCKET_NAME}`], + { + env, + stdio: ['ignore', 'pipe', 'pipe'], + }, + ); + let westonStderr = ''; + let westonExited = false; + weston.once('exit', () => { + westonExited = true; + }); + // QNBS-v3: spawn() reports a missing/non-executable program via an async 'error' event, not a thrown exception β€” without a listener Node crashes with a raw ENOENT stack trace instead of this script's own FAIL diagnostic, exactly the failure mode this probe exists to report cleanly β€” CodeRabbit review finding on PR #393. + weston.on('error', (err) => { + westonStderr += `spawn error: ${err.message}\n`; + }); + weston.stderr.on('data', (chunk) => { + westonStderr += chunk.toString(); + }); + + const socketDeadline = Date.now() + COMPOSITOR_SOCKET_GRACE_MS; + // QNBS-v3: also bails out early if Weston itself already exited β€” otherwise this loop would keep polling for a socket a dead process will never create, wasting the whole grace period on a doomed wait β€” CodeAnt review finding on PR #393 (the same "verify the process is actually alive" half of the finding). + while (!fs.existsSync(socketPath) && !westonExited && Date.now() < socketDeadline) { + await sleep(200); + } + if (westonExited || !fs.existsSync(socketPath)) { + console.error( + `[wayland-smoke] FAIL β€” Weston ${westonExited ? 'exited before creating' : 'did not create'} ${socketPath} in time.`, + ); + if (westonStderr) console.error(`[wayland-smoke] Weston stderr:\n${westonStderr}`); + weston.kill('SIGKILL'); + process.exit(1); + } + console.log(`[wayland-smoke] Weston compositor socket ready: ${socketPath}`); + + console.log(`[wayland-smoke] Launching worldscript_host with --ozone-platform=wayland…`); + const child = spawn( + binaryPath, + [`--url=${url}`, '--ozone-platform=wayland', '--enable-logging=stderr', '--v=1'], + { + cwd: path.dirname(binaryPath), + stdio: ['ignore', 'pipe', 'pipe'], + env: { ...env, WAYLAND_DISPLAY: WAYLAND_SOCKET_NAME }, + }, + ); + + let stdout = ''; + let stderr = ''; + // QNBS-v3: same rationale as weston's 'error' listener above β€” a missing/non-executable binaryPath must surface through this script's own FAIL diagnostic, not an unhandled ENOENT crash. + child.on('error', (err) => { + stderr += `spawn error: ${err.message}\n`; + }); + child.stdout.on('data', (chunk) => { + stdout += chunk.toString(); + }); + child.stderr.on('data', (chunk) => { + stderr += chunk.toString(); + }); + + const exited = new Promise((resolve) => + child.once('exit', (code, signal) => resolve({ code, signal })), + ); + + const rendered = await Promise.race([ + (async () => { + while (!(stdout.includes(FFI_PROOF_LINE) && stdout.includes(EXPECTED_TITLE_LINE))) { + await sleep(200); + } + return true; + })(), + exited.then(() => false), + sleep(LAUNCH_GRACE_MS).then(() => false), + ]); + + weston.kill('SIGKILL'); + sweepMatchingProcesses(); + // QNBS-v3: verify-and-resweep, not a single one-shot pass β€” CodeAnt review finding on PR #393 (a subprocess created between the first pgrep snapshot and its kill, or one still in the middle of exiting, would otherwise survive undetected). + await sleep(CLEANUP_RECHECK_GRACE_MS); + if (sweepMatchingProcesses()) { + console.error( + '[wayland-smoke] WARNING β€” worldscript_host process(es) still matched after cleanup; sent a second SIGKILL sweep.', + ); + } + + if (!rendered) { + console.error( + `[wayland-smoke] FAIL β€” did not observe both "${FFI_PROOF_LINE}" and "${EXPECTED_TITLE_LINE}" within ${LAUNCH_GRACE_MS}ms under --ozone-platform=wayland.`, + ); + console.error(`[wayland-smoke] stdout:\n${stdout || '(empty)'}`); + console.error(`[wayland-smoke] stderr:\n${stderr || '(empty)'}`); + process.exit(1); + } + + console.log( + '[wayland-smoke] OK β€” worldscript_host rendered the real production bundle under a headless Weston Wayland compositor (--ozone-platform=wayland), FFI boundary and title both proven.', + ); +} + +main().catch((err) => { + console.error(`[wayland-smoke] FAIL β€” unexpected error: ${err.message}`); + process.exit(1); +}); diff --git a/turbo.json b/turbo.json index d96041cb..6aad271d 100644 --- a/turbo.json +++ b/turbo.json @@ -16,7 +16,8 @@ "RUN_DEEP_E2E", "RUN_MOBILE_E2E", "RUN_REAL_VOICE_E2E", - "SMOKE_PORT" + "SMOKE_PORT", + "XDG_RUNTIME_DIR" ], "tasks": { "build": {