diff --git a/apps/desktop-cef/src/worldscript_handler.cpp b/apps/desktop-cef/src/worldscript_handler.cpp index b3a06cc7c..5b0d7e88f 100644 --- a/apps/desktop-cef/src/worldscript_handler.cpp +++ b/apps/desktop-cef/src/worldscript_handler.cpp @@ -4,6 +4,7 @@ #include #include "include/cef_app.h" +#include "include/cef_browser.h" #include "include/cef_task.h" #include "include/wrapper/cef_helpers.h" @@ -61,6 +62,11 @@ void WorldScriptHandler::OnAfterCreated(CefRefPtr browser) { printf("[worldscript_host] rust_core ping = %d\n", worldscript_rust_ping()); fflush(stdout); + // QNBS-v3: CefClient::GetAccessibilityHandler doesn't exist (PR #391's real finding) — that method is on CefRenderHandler instead, which is OSR-only ("when window rendering is disabled", include/cef_render_handler.h) and doesn't apply to this Views-based windowed host. SetAccessibilityState's own doc comment (include/cef_browser.h) confirms windowed browsers need only this one call: "all platform accessibility objects will be created and managed by Chromium's internal implementation" — no CefAccessibilityHandler required. + browser->GetHost()->SetAccessibilityState(STATE_ENABLED); + printf("[worldscript_host] accessibility_state_requested = true\n"); + fflush(stdout); + CefPostDelayedTask(TID_UI, new PollShutdownTask(this), kShutdownPollIntervalMs); } diff --git a/docs/architecture/native-readiness.md b/docs/architecture/native-readiness.md index 27b82e7af..b8adcf616 100644 --- a/docs/architecture/native-readiness.md +++ b/docs/architecture/native-readiness.md @@ -60,7 +60,7 @@ Wave 2's first deliverable — the CEF binding/C++ decision — is now backed by | 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`. | +| Early Accessibility Gate | **PASS** — state enablement only, tree observability open | cef-runtime, Wave 2 | PR #391 found the real root cause: `GetAccessibilityHandler()` is declared on `CefRenderHandler` (OSR-only, "when window rendering is disabled"), not `CefClient` — never reachable from this windowed host regardless of what was inherited. PR #397: `CefBrowserHost::SetAccessibilityState(STATE_ENABLED)` alone is windowed-mode-correct per its own doc comment; CI-proven in every one of 3 repeated cycles with zero regression to the FFI/rendering/crash-reporting/Wayland proofs. Does **not** yet prove the platform accessibility tree is observable — that needs OS-level AT-SPI introspection on Linux, separate unattempted follow-up — 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. | | Crash reporting / renderer-crash resilience | **PASS** — crash-reporting half only | cef-runtime | PR #392: `crash_reporter.cfg` + `CefCrashReportingEnabled()` verified true, `chrome://crash` deliberately crashes the renderer, `CefRequestHandler::OnRenderProcessTerminated` fires (`TS_PROCESS_CRASHED`), the browser process/message loop survive, and a real Crashpad `.dmp` file — the harness's actual assertion, alongside Crashpad's own `.meta`/`settings.dat` housekeeping files (observed, not independently asserted) — is produced under an overridden `BREAKPAD_DUMP_LOCATION`; all CI-run, not a doc claim. Symbolization (decoding the dump into a stack trace via `dump_syms`/`minidump_stackwalk`) needs a full Chromium source checkout and was not attempted — see `docs/cef/knowledge/cef-architecture-primer.md`. | | CEF SDK fetch/verify + version diagnostics automated | **PASS** | cef-runtime | `🧪 CEF Learning Harness` CI job (`.github/workflows/cef-learning-harness.yml`) fetches the pinned CEF SDK, verifies its checksum, and parses real version macros out of the extracted `include/cef_version.h` — a genuine CI-run check, not a doc claim. | @@ -68,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**: 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. +**Overall for this snapshot**: 8 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; Early Accessibility Gate explicitly PASS for state enablement only, not tree observability), 2 explicit `DEBT — partial` rows (each with a concrete exit condition, not open-ended), 1 row marked `Not yet attempted` (Sandbox posture) 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 9cf0212f8..105035b7f 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/#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. +**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/#397), 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. @@ -14,7 +14,7 @@ cef_competency: repeated_shutdown_ci: true # scripts/cef/run-launch-cycle-proof.mjs, cef-learning-harness CI job (PR #388) renderer_crash_ci: true # chrome://crash + OnRenderProcessTerminated + browser-process survival, cef-learning-harness CI job (PR #392) sandbox_smoke: false - accessibility_smoke: false + accessibility_smoke: false # state ENABLEMENT is proven (PR #397, SetAccessibilityState, 3/3 CI cycles) — this field is specifically about the platform accessibility tree being observable, which needs OS-level AT-SPI introspection and was not attempted crash_symbolization_smoke: false # crash REPORTING is proven (PR #392) — this field is specifically about decoding a dump (dump_syms/minidump_stackwalk), which needs a full Chromium source checkout and was not attempted ``` @@ -27,7 +27,7 @@ CI validation of this block ("fail CI when a required item for the active progra | CEF architecture (process model, browser/frame/client ownership, message loop, shutdown ordering, subprocess packaging, sandbox expectations) | Partial | Process model, message loop, and shutdown ordering all have real working code + CI proof (`apps/desktop-cef/`, PR #388), now written up in `docs/cef/knowledge/cef-architecture-primer.md` (PR #390, no longer a skeleton). Subprocess *resource layout* (unpackaged CEF build output — `COPY_FILES`) is confirmed, but real shipped/installer packaging is separate, unproven, later scope. Sandbox expectations still have zero evidence. | | CEF threading & lifetime rules (UI-thread callbacks, IO thread, ref-counted objects, callback lifetime, async cancellation, shutdown races) | Partial | `CEF_REQUIRE_UI_THREAD()` used throughout; `IMPLEMENT_REFCOUNTING`/`CefRefPtr` applied correctly; a real callback-lifetime lesson learned and fixed (`base::Unretained` vs. a plain `CefTask` — see `apps/desktop-cef/src/worldscript_handler.cpp`), now written up in `docs/cef/knowledge/threading-and-lifetimes.md` (PR #390, no longer a skeleton). IO thread, render-process-side code, and async-cancellation patterns remain untouched. | | Rust binding layer (crate/version, unsafe/FFI boundary, wrapper ownership, API coverage gaps, upgrade procedure) | Partial | `apps/desktop-cef/rust-core/` (`worldscript_rust_core`, Corrosion-linked) — FFI boundary proven inside the real CEF host in CI (PR #388), not just an isolated test. No upgrade procedure written yet (`docs/cef/knowledge/binding-upgrade-playbook.md` still skeleton); API coverage is currently one trivial function, not representative of real surface area. | -| Cross-platform native host (Linux loader/resource layout, Windows process/installer/sandbox, macOS bundle/signing, window lifecycle, high-DPI, IME/a11y) | Partial (Linux only) | Linux loader/resource layout confirmed via a real filesystem listing in CI (`docs/cef/knowledge/linux-runtime-notes.md`); a real cwd-relative-path startup bug found and fixed. Zero Windows/macOS evidence. Window lifecycle proven for open/close only — high-DPI and IME/a11y untouched. | +| Cross-platform native host (Linux loader/resource layout, Windows process/installer/sandbox, macOS bundle/signing, window lifecycle, high-DPI, IME/a11y) | Partial (Linux only) | Linux loader/resource layout confirmed via a real filesystem listing in CI (`docs/cef/knowledge/linux-runtime-notes.md`); a real cwd-relative-path startup bug found and fixed. Zero Windows/macOS evidence. Window lifecycle proven for open/close only. Accessibility: state enablement proven (PR #397), tree observability (AT-SPI) and IME both still untouched. High-DPI untouched. | | Operational CEF (crash reporting, symbol handling, version-update automation, sandbox verification, packaging deps, runtime diagnostics) | Partial | Packaging deps: `scripts/cef/check-linux-runtime-deps.mjs` (dpkg package presence) + `scripts/cef/check-linux-runtime-linkage.mjs` (PR #395 — real `ldd` against the CI-built runtime artifacts `worldscript_host` and `libcef.so`, both fully resolved on the CI runner), both CI-run. Runtime diagnostics: `scripts/cef/print-cef-version-diagnostics.mjs` + verbose CEF logging (`--enable-logging=stderr --v=1`) added mid-debugging this wave. Crash reporting: proven in CI (PR #392) — `crash_reporter.cfg` + `CefCrashReportingEnabled()` + a deliberately induced renderer crash (`chrome://crash`) produced a real Crashpad `.dmp` file (the harness's actual assertion) under an overridden `BREAKPAD_DUMP_LOCATION`, alongside Crashpad's own `.meta`/`settings.dat` housekeeping files (observed, not independently asserted); the browser process survived. Symbol handling (decoding a dump into a stack trace) needs `dump_syms`/`minidump_stackwalk` built from a full Chromium source checkout — out of reach of this project's minimal-CEF-SDK CI setup, not attempted. Version-update automation and sandbox verification remain not started. | ## Appendix A.1 checklist (live) @@ -42,7 +42,7 @@ CI validation of this block ("fail CI when a required item for the active progra [x] Unpackaged CEF resource layout proven — PR #388, real filesystem listing in CI (real shipped/installer packaging remains separate, unproven, later scope) [x] Repeated startup/shutdown harness green — PR #388, 3/3 cycles, cef-learning-harness CI job [x] Renderer crash observation green — PR #392, chrome://crash + OnRenderProcessTerminated (TS_PROCESS_CRASHED), browser process survived, cef-learning-harness CI job -[ ] Accessibility smoke green (attempted, real blocker — see cef-architecture-primer.md's "Accessibility API" section) +[ ] Accessibility smoke green (state enablement proven, PR #397, 3/3 CI cycles, zero regression; tree observability — AT-SPI introspection — still open, 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 + ldd-verified against the real CI-built runtime artifacts (worldscript_host, libcef.so), PR #395 — still one distro/runner image, no packaged-installer declaration; see native-readiness.md) [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. @@ -62,7 +62,7 @@ CI validation of this block ("fail CI when a required item for the active progra [x] renderer termination observed and handled — PR #392, chrome://crash deliberately crashes the renderer, OnRenderProcessTerminated fires, browser process/message loop survive, cef-learning-harness CI job [ ] sandbox development plan validated [ ] Linux runtime dependencies inventoried (inventoried + ldd-verified against the real CI-built runtime artifacts (worldscript_host, libcef.so), PR #395 — still one distro/runner image, no packaged-installer declaration; see native-readiness.md) -[ ] at least one accessibility smoke test performed (attempted, real blocker — see cef-architecture-primer.md's "Accessibility API" section) +[ ] at least one accessibility smoke test performed (state enablement proven, PR #397, 3/3 CI cycles, zero regression; tree observability — AT-SPI introspection — still open, see cef-architecture-primer.md's "Accessibility API" section) [x] at least one crash-reporting/symbolization path proven — PR #392: crash-reporting path proven end-to-end (real Crashpad dump produced in CI); full symbolization (decoding the dump) is a separate, unattempted step needing a full Chromium source checkout [ ] upgrade playbook exists ``` @@ -76,7 +76,7 @@ Superseding the original "Wave 0 does not claim" list, now that some of those it - The CEF integration approach **has** been selected (Option B, ADR-0020) — this is no longer an open item. - A learning harness **does** exist and runs in CI (`cef-learning-harness`, PR #388) — this is no longer an open item. - Still true: no external-expertise engagement has been triggered — none of the escalation criteria (roadmap §4.11.5) have occurred. -- Still true, and still the most important caveat: this is Linux-only (X11 proven since PR #388, Wayland smoke also proven PR #393 — but one virtual CI runner, no real GPU/hardware matrix), `no_sandbox=true` throughout, no dump symbolization (crash *reporting* is proven), no working accessibility mechanism (a real CEF-151 API blocker was found and documented, not silently skipped), and no upgrade-playbook prose exists yet (`docs/cef/knowledge/binding-upgrade-playbook.md` remains a skeleton — architecture, threading, and binding-cookbook docs are no longer skeletons, PR #390). The competency gate above is explicitly **not** satisfied. +- Still true, and still the most important caveat: this is Linux-only (X11 proven since PR #388, Wayland smoke also proven PR #393 — but one virtual CI runner, no real GPU/hardware matrix), `no_sandbox=true` throughout, no dump symbolization (crash *reporting* is proven), no accessibility-tree observability (state *enablement* is proven, PR #397 — its own real root cause was found rather than staying blocked, but the tree itself is still unverified), and no upgrade-playbook prose exists yet (`docs/cef/knowledge/binding-upgrade-playbook.md` remains a skeleton — architecture, threading, and binding-cookbook docs are no longer skeletons, PR #390). The competency gate above is explicitly **not** satisfied. - This matrix will continue to be updated in place as each item is genuinely satisfied, with a link to the proving test/CI job/doc (roadmap §61.1.4 evidence-link pattern) — not marked done on intention alone. ## Update discipline diff --git a/docs/cef/OWNERSHIP.yaml b/docs/cef/OWNERSHIP.yaml index 0d5b3f823..8d2fecbf7 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/#393) — 4 of 7 cef_competency items now true with linked evidence (lifetime_model_reviewed and renderer_crash_ci added); doc-sync fix flipped 2 more Appendix A.1/gate items to checked once cef-architecture-primer.md/threading-and-lifetimes.md were noticed to already have real content from PR #390 (previously left unchecked on a stale 'no dedicated doc yet' annotation); 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 (8/12)." + note: "Updated in place for Wave 2 (PR #386/#387/#388/#391/#392/#393/#397) — 4 of 7 cef_competency items now true with linked evidence (lifetime_model_reviewed and renderer_crash_ci added); doc-sync fix flipped 2 more Appendix A.1/gate items to checked once cef-architecture-primer.md/threading-and-lifetimes.md were noticed to already have real content from PR #390 (previously left unchecked on a stale 'no dedicated doc yet' annotation); Wayland smoke checked in Appendix A.1 (PR #393); accessibility_smoke stays false (PR #397 proved state enablement only, not tree observability — same half-proven convention as crash_symbolization_smoke); competency gate still not satisfied (8/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); 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." + 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); accessibility state enablement proven in CI, zero regression (PR #397). Sandbox config, symbolization, a real GPU/compositor matrix, accessibility-tree observability (AT-SPI), 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 91e993feb..2beb87ac3 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); 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. +**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; accessibility state enablement proven in CI with zero regression (PR #397). Sandbox configuration, dump symbolization, a real GPU/compositor matrix, accessibility-tree observability (AT-SPI), 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. @@ -29,13 +29,17 @@ This single-binary-multi-role design is directly why `scripts/cef/run-launch-cyc A real launch-path bug was found and fixed here too: Chromium resolves several of these resource paths relative to the process's *working directory*, not the executable's own location — launching the binary from a different cwd produced an ICU-init crash despite every file being correctly present. See `docs/cef/knowledge/linux-runtime-notes.md` for the full finding. -## Accessibility API — a real blocker found, not yet worked around +## Accessibility API — state enablement proven; tree observability open -An attempt was made (2026-08-19, PR #391) to implement `CefAccessibilityHandler` on `WorldScriptHandler`, returned from a `CefClient::GetAccessibilityHandler()` override, following the pattern every other handler type in this codebase uses (`GetLifeSpanHandler`, `GetDisplayHandler`). **This does not compile against CEF 151.3.18** — the actual compiler error was explicit: `'CefRefPtr WorldScriptHandler::GetAccessibilityHandler()' marked 'override', but does not override`. `CefClient` simply does not declare this getter in this CEF version, contradicting the assumption (drawn from older CEF documentation/examples) that it would. +**First attempt (2026-08-19, PR #391)**: implemented `CefAccessibilityHandler` on `WorldScriptHandler`, returned from a `CefClient::GetAccessibilityHandler()` override, following the pattern every other handler type in this codebase uses (`GetLifeSpanHandler`, `GetDisplayHandler`). **This does not compile against CEF 151.3.18** — the actual compiler error was explicit: `'CefRefPtr WorldScriptHandler::GetAccessibilityHandler()' marked 'override', but does not override`. A same-PR fallback (`SetAccessibilityState` + `--force-renderer-accessibility`, no observability) caused an unrelated regression in the FFI/rendering proofs; both changes were fully reverted rather than left half-working. -The attempt was fully reverted rather than left half-working: `browser->GetHost()->SetAccessibilityState(STATE_ENABLED)` and a `--force-renderer-accessibility` command-line switch (both real, confirmed-to-compile CEF/Chromium APIs, unrelated to the `GetAccessibilityHandler` issue) were tried as a fallback with no observability attempt, but the very next CI run showed the previously 100%-reliable FFI-boundary and rendering proofs failing to produce any output at all — a regression serious enough that isolating its exact cause needs a real CEF SDK to compile and test against locally, not another blind CI round-trip on this constrained dev machine (see ADR-0020's own disk/RAM constraints). `apps/desktop-cef/` was reverted to byte-identical with what's on `main`. +**Root cause, found on the second attempt (2026-08-19, PR #397)**: `GetAccessibilityHandler()` is real, but it's declared on `CefRenderHandler`, not `CefClient` (`include/cef_render_handler.h`, verified against the pinned CEF branch's actual source) — and `CefRenderHandler`'s own doc comment states it is for "handling events when window rendering is disabled" (OSR mode only). `worldscript_host` uses windowed rendering (`CefBrowserView`/`CefWindow`), so that method was never reachable here regardless of what `WorldScriptHandler` inherited from — PR #391's blocker had a real, findable, version-independent cause, not a moving target. -**What this means for the next attempt**: the correct modern CEF 151 mechanism for accessibility tree observation is genuinely unknown as of this doc's writing — it needs real API research (current CEF source/docs, not assumptions carried from older versions or generic Chromium knowledge) before writing any more code against it. `accessibility_smoke` stays `false` in the competency manifest; the Early Accessibility Gate remains unattempted-with-a-working-mechanism, not "attempted and passed." +**What PR #397 does instead**: `CefBrowserHost::SetAccessibilityState(STATE_ENABLED)` alone, called in `OnAfterCreated` — `SetAccessibilityState`'s own doc comment (`include/cef_browser.h`) confirms windowed browsers need only this one call: "all platform accessibility objects will be created and managed by Chromium's internal implementation," no `CefAccessibilityHandler` required. This is a small, isolated addition (one call, one proof line, no new class inheritance). + +**Directly observed evidence, PR #397, `🧪 CEF Learning Harness` CI job**: `[launch-cycle-proof] OK — 3/3 repeated start/close cycles clean, FFI boundary, real rendering, and accessibility-state request all proven in every cycle.` The crash-reporting and Wayland proofs both remained green in the same run — this addition caused no regression to the already-proven proofs (unlike PR #391's fallback attempt). One transient first-attempt CI timeout on cycle 1 (browser never reached `OnAfterCreated` within the grace period) reproduced the exact shape of the runner-speed variance already documented for `STARTUP_GRACE_MS` elsewhere in this file, and did not recur on a clean re-run — treated as CI-runner noise, not a code-shaped regression, consistent with that established precedent. + +**What this proves**: accessibility state can be enabled intentionally, on every launch, with zero regression to existing proofs (roadmap §23.1's first bullet). **What it does NOT prove**: that the platform accessibility tree is actually observable — Chromium's windowed-mode accessibility integration registers with the OS's native accessibility bus (AT-SPI on Linux) rather than exposing a CEF-level callback, so verifying the tree exists needs OS-level AT-SPI introspection in CI, not a C++ handler. That remains separate, unattempted follow-up work. `accessibility_smoke` stays `false` in the competency manifest until both halves have real evidence. ## Crash reporting — a real, working proof (with an honest limit) diff --git a/scripts/cef/run-launch-cycle-proof.mjs b/scripts/cef/run-launch-cycle-proof.mjs index 8b3f8586f..5cc494916 100644 --- a/scripts/cef/run-launch-cycle-proof.mjs +++ b/scripts/cef/run-launch-cycle-proof.mjs @@ -9,7 +9,7 @@ * (docs/cef/knowledge/subprocess-and-shutdown.md): an immediate post-signal check is * a false positive, not evidence of a hang. * - * Each cycle must independently show two proofs, not just "at least one cycle across + * Each cycle must independently show three proofs, not just "at least one cycle across * the whole run" (a real review finding — masking a later cycle's failure behind an * earlier success would make the proof meaningless): * - FFI boundary: apps/desktop-cef/src/worldscript_handler.cpp's OnAfterCreated @@ -18,12 +18,17 @@ * error page (bad bundle, load failure) would not produce that specific title, * so this catches "CEF started but the app didn't actually render" failures the * FFI proof alone cannot. + * - Accessibility state requested: SetAccessibilityState(STATE_ENABLED) is called on + * every cycle (see the Early Accessibility Gate note below). * - * Early Accessibility Gate (roadmap §3142): apps/desktop-cef does not request accessibility - * tree construction today — an attempt was made and fully reverted after CefClient turned - * out to have no GetAccessibilityHandler() in this CEF version (151.3.18), so the - * callback-based signal this harness would have checked for does not exist; see - * docs/cef/knowledge/cef-architecture-primer.md for the real, honestly-documented blocker. + * Early Accessibility Gate (roadmap §3142): apps/desktop-cef's OnAfterCreated calls + * browser->GetHost()->SetAccessibilityState(STATE_ENABLED) on every cycle — the real, + * windowed-mode-appropriate API (CefClient has no GetAccessibilityHandler(); that method + * is on CefRenderHandler, which is OSR-only and doesn't apply to this Views-based host — + * see docs/cef/knowledge/cef-architecture-primer.md). This proves accessibility state can + * be enabled intentionally (roadmap §23.1's first bullet); it does not yet prove the + * platform accessibility tree is actually observable — that needs OS-level AT-SPI + * introspection, not a CEF callback, and is separate follow-up work. * * After the repeated cycles, one additional crash-reporting proof runs * (runCrashReportingProofCycle): launches with chrome://crash to deliberately crash @@ -55,6 +60,8 @@ const SHUTDOWN_GRACE_MS = 6000; const ORPHAN_CHECK_GRACE_MS = 3000; const FFI_PROOF_LINE = 'rust_core ping = 424242'; const EXPECTED_TITLE_LINE = 'title = WorldScript Studio'; +// QNBS-v3: proves SetAccessibilityState(STATE_ENABLED) is requested on every cycle, not just once — same "no cycle can mask another" discipline as the FFI/title proofs above. +const ACCESSIBILITY_STATE_PROOF_LINE = 'accessibility_state_requested = true'; // QNBS-v3: crash-reporting/symbolization competency-gate proof (CEF-RUST-COMPETENCY-MATRIX.md) — mechanism verified against real CEF 151 source (libcef/common/crash_reporting.cc, crash_reporter_client.cc), not assumed; see docs/cef/knowledge/cef-architecture-primer.md. const CRASH_REPORTING_ENABLED_LINE = 'crash_reporting_enabled = true'; @@ -153,6 +160,8 @@ async function runCycle(index) { const exited = new Promise((resolve) => child.once('exit', (code, signal) => resolve({ code, signal })), ); + // QNBS-v3: 'exit' fires as soon as the process terminates but its stdio streams can still be open (buffered output not yet fully delivered); 'close' is the guarantee all of it has arrived — CodeRabbit review finding on PR #397, backed by real Node.js child_process docs and a reproduction. Only gates the stdout marker checks below, not the exit/signal checks above, which are legitimately about process termination itself. + const closed = new Promise((resolve) => child.once('close', () => resolve())); // QNBS-v3: races against the startup grace period so an immediate crash is caught here, distinct from a deliberate SIGTERM-driven exit later — Qodo review finding on PR #388 ("crashed cycles count clean"). const earlyExit = await Promise.race([exited, sleep(STARTUP_GRACE_MS).then(() => null)]); @@ -185,6 +194,18 @@ async function runCycle(index) { throw new Error(`Cycle ${index + 1}: orphaned worldscript_host process(es) still running.`); } + // QNBS-v3: waits for 'close' (all stdio fully drained), not just 'exit' — see the QNBS-v3 comment where `closed` is declared above. Bounded rather than awaited outright, matching this file's established defensive-timeout convention even though close should already have fired well within ORPHAN_CHECK_GRACE_MS in practice. + const streamsClosed = await Promise.race([ + closed.then(() => true), + sleep(2000).then(() => false), + ]); + if (!streamsClosed) { + logStderr(`Cycle ${index + 1}`, stderr); + throw new Error( + `Cycle ${index + 1}: stdio streams did not close within 2000ms after process exit.`, + ); + } + // QNBS-v3: required per cycle, not aggregated across the whole run — Qodo review finding on PR #388 ("FFI proof is not repeated"); one cycle's success must never mask another cycle's failure. if (!stdout.includes(FFI_PROOF_LINE)) { logStderr(`Cycle ${index + 1}`, stderr); @@ -196,8 +217,14 @@ async function runCycle(index) { `Cycle ${index + 1}: expected "${EXPECTED_TITLE_LINE}" not observed — the production bundle may not have rendered (a CEF error page would not produce this specific title).`, ); } + if (!stdout.includes(ACCESSIBILITY_STATE_PROOF_LINE)) { + logStderr(`Cycle ${index + 1}`, stderr); + throw new Error( + `Cycle ${index + 1}: expected "${ACCESSIBILITY_STATE_PROOF_LINE}" not observed — SetAccessibilityState(STATE_ENABLED) was not requested.`, + ); + } console.log( - `[launch-cycle-proof] Cycle ${index + 1}/${cycles}: clean exit (signal=${shutdownResult.signal}), FFI + rendering proofs both present.`, + `[launch-cycle-proof] Cycle ${index + 1}/${cycles}: clean exit (signal=${shutdownResult.signal}), FFI + rendering + accessibility-state proofs all present.`, ); } @@ -348,7 +375,7 @@ async function main() { } console.log( - `[launch-cycle-proof] OK — ${cycles}/${cycles} repeated start/close cycles clean, FFI boundary and real rendering both proven in every cycle.`, + `[launch-cycle-proof] OK — ${cycles}/${cycles} repeated start/close cycles clean, FFI boundary, real rendering, and accessibility-state request all proven in every cycle.`, ); await runCrashReportingProofCycle();