diff --git a/.github/workflows/cef-learning-harness.yml b/.github/workflows/cef-learning-harness.yml index aa7903842..e3f29cd49 100644 --- a/.github/workflows/cef-learning-harness.yml +++ b/.github/workflows/cef-learning-harness.yml @@ -108,6 +108,15 @@ jobs: - name: Clean-machine Linux dependency inventory run: node scripts/cef/check-linux-runtime-deps.mjs + # QNBS-v3: diagnostic-only, no behavior change — real evidence on whether this runner can + # even support Chromium's Linux sandbox (Sandbox posture row, native-readiness.md, "Not yet + # attempted") before any attempt to actually enable it. Non-fatal by design. + - name: Linux sandbox feasibility inventory (diagnostic only) + continue-on-error: true + run: | + set -o pipefail + node scripts/cef/check-linux-sandbox-inventory.mjs | tee "$RUNNER_TEMP/cef-sandbox-inventory.txt" + - name: Restore CEF SDK cache id: cef-cache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -227,5 +236,9 @@ jobs: echo "- worldscript_host built and repeated launch/close cycles proven against the real production bundle (dist/), under Xvfb." >> "$GITHUB_STEP_SUMMARY" echo "- Linux runtime linkage (ldd against the shipped worldscript_host + libcef.so, not just dpkg package presence): see the \"Linux runtime linkage check\" step above." >> "$GITHUB_STEP_SUMMARY" echo "- Crash-symbolization proof: a self-induced browser-process crash resolved via dump_syms + minidump-stackwalk against our own DWARF debug info — Chromium/CEF-internal frames remain unsymbolized (no debug-symbols archive is published for this distribution)." >> "$GITHUB_STEP_SUMMARY" + echo "- Linux sandbox feasibility inventory (diagnostic only, no sandbox behavior attempted yet):" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + cat "$RUNNER_TEMP/cef-sandbox-inventory.txt" >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || echo "(inventory output unavailable)" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$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-tree observability (AT-SPI — state enablement is proven, see the launch-cycle-proof step)." >> "$GITHUB_STEP_SUMMARY" diff --git a/docs/architecture/native-readiness.md b/docs/architecture/native-readiness.md index 46b906826..0460a2e84 100644 --- a/docs/architecture/native-readiness.md +++ b/docs/architecture/native-readiness.md @@ -61,11 +61,11 @@ Wave 2's first deliverable — the CEF binding/C++ decision — is now backed by | 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 | **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. | +| 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. PR #402: diagnostic-only feasibility inventory confirmed `unshare --user --pid --fork` succeeds on the CI runner (functional test, not just a sysctl read) — unprivileged user-namespace sandboxing appears reachable, but no sandbox behavior has been attempted yet. **Acceptance bar for the follow-up enable attempt** (not this row's current status): not just "CEF starts without `no_sandbox=true`" — must show browser/renderer/utility-GPU processes actually running under the sandbox (Chromium's own recommendation: check real per-process sandbox status, e.g. `chrome://sandbox` or an equivalent CLI-observable signal — Linux combines namespace isolation *and* seccomp-BPF, and both matter), zero regression to the existing lifecycle/crash/accessibility/Wayland proofs, and a reproducible sandbox-status proof in CI. Explicitly disallowed: "fixing" a launch failure by silently trading `no_sandbox=true` for a narrower blanket disable like `--disable-setuid-sandbox` while still claiming the row as proven — that would misrepresent what's actually protected. **CI-sandbox-proof and production-packaging-sandbox-proof are two separate gates** — a GitHub Actions runner can prove "our CEF config can run sandboxed," not "our eventual .deb/AppImage/installer distribution correctly installs the helper/permissions/runtime layout on every target distro." The latter is real packaging scope, not to be pulled into Wave 2. | | Crash reporting / renderer-crash resilience / symbolization | **PASS** — Chromium-internal frames excepted | 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. PR #400: symbolization also proven — the initial "needs a full Chromium checkout" assumption was wrong for our own code's frames; `dump_syms`/`minidump-stackwalk` (standalone Rust tools, no Chromium checkout) resolved a self-induced browser-process crash (`--debug-crash-self`) end-to-end back to the crashing function's name. Chromium/CEF-internal frames remain genuinely unsymbolized — no distribution type ships debug symbols, verified against CEF's own build index — 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. | -| Linux dependency inventory — clean-machine data point | DEBT — partial | cef-runtime | Same CI job runs the package-presence check against a stock `ubuntu-latest` runner before any `apt-get`, adding a real second data point beyond the spike's one already-configured dev machine. PR #395 added the specific check this row previously flagged as missing: `scripts/cef/check-linux-runtime-linkage.mjs` runs `ldd` against the real, already-built `worldscript_host` and `libcef.so` — both fully resolved on the runner, zero unresolved dependencies. Still narrow: one distro/runner image only, no packaged-installer dependency declaration. | +| Linux dependency inventory (Wave 2 scope) — clean-machine data point | **PASS** — single distro/runner only, packaged-installer declaration excepted | cef-runtime | Same CI job runs the package-presence check against a stock `ubuntu-latest` runner before any `apt-get`, adding a real second data point beyond the spike's one already-configured dev machine. PR #395 added the specific check this row previously flagged as missing: `scripts/cef/check-linux-runtime-linkage.mjs` runs `ldd` against the real, already-built `worldscript_host` and `libcef.so` — both fully resolved on the runner, zero unresolved dependencies. Split 2026-08-19 (`CEF-RUST-COMPETENCY-MATRIX.md`'s own gate item split the same way): this row previously stayed DEBT pending "a packaged-installer dependency declaration," which is a different, later packaging-wave goal (matching this table's own existing convention for Wayland below — proven on what Wave 2 actually needs, one CI runner, not blocked pending a broader matrix). One distro/runner image only; a real multi-distro packaged-installer compatibility proof is separate, later scope, tracked as its own open item, not this row. | | 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**: 8 PASS (crash reporting/symbolization explicitly PASS except for Chromium-internal frames, which no CEF distribution ships debug symbols for; 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. +**Overall for this snapshot**: 9 PASS (crash reporting/symbolization explicitly PASS except for Chromium-internal frames, which no CEF distribution ships debug symbols for; 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; Linux dependency inventory explicitly PASS for Wave 2 scope only, split 2026-08-19 from the separate packaged-installer-declaration goal — see that row), 1 explicit `DEBT — partial` row (with a concrete exit condition, not open-ended), 1 row marked `Not yet attempted` (Sandbox posture, though PR #402 validated real feasibility — see that row) rather than assumed. No row is marked PASS without the evidence cited above. diff --git a/docs/cef/CEF-RISK-REGISTER.md b/docs/cef/CEF-RISK-REGISTER.md index 22aa13f7b..22a35c5a7 100644 --- a/docs/cef/CEF-RISK-REGISTER.md +++ b/docs/cef/CEF-RISK-REGISTER.md @@ -13,14 +13,14 @@ Every P0/P1 risk below must have an owner, a test, and an exit condition before | R-03 | Unrestricted/unvalidated IPC surface | Critical | OPEN | *unassigned* | Typed allowlist, schema validation, fuzzing | Bridge contract tests (§52) pass; unknown methods rejected | | R-04 | CEF packaging complexity across 3 platforms | High | OPEN | *unassigned* | Incremental cross-platform CI | Wave 12 packaged artifacts install/launch/uninstall clean on all 3 platforms | | R-05 | Chromium security patch cadence falls behind upstream | High | OPEN | *unassigned* | Automated monitoring + emergency update lane (§34.1) | Security-SLA dashboard green, no overdue exception (Appendix M.1) | -| R-06 | CEF/Rust/C++ lifetime defects (UB, use-after-free, shutdown races) | High | OPEN | *unassigned* | Minimal wrapper surface, competency harness, dual-review (§4.11.4) | Learning harness (§61.1) green in CI across repeated start/stop cycles | +| R-06 | CEF/Rust/C++ lifetime defects (UB, use-after-free, shutdown races) | High | MITIGATING | cef-runtime (backup: rust-core) | Minimal wrapper surface, competency harness, dual-review (§4.11.4). Real evidence now exists: `scripts/cef/run-launch-cycle-proof.mjs` (3/3 repeated start/close cycles, `cef-learning-harness` CI job, PR #388+); a real callback-lifetime bug found and fixed (`base::Unretained` vs. a plain `CefTask`, `docs/cef/knowledge/threading-and-lifetimes.md`, PR #390). | This row's own exit condition is met for the scope it covers — reassessed 2026-08-19 (Wave 2 in progress, not deferred to a later wave per this register's own "assign before the corresponding wave begins" rule). **Not CLOSED**: IO-thread and render-process-side lifetime rules, and async-cancellation patterns, remain untouched (`CEF-RUST-COMPETENCY-MATRIX.md`'s threading domain is "Partial", not complete) — the risk is real-mitigated for the covered surface, not retired. | | R-07 | Memory regression vs. current baseline | High | OPEN | *unassigned* | Per-process attribution (§28), soak tests, numeric budgets | Wave 15 soak passes, no unbounded growth over 2h | | R-08 | GPU process instability (Wayland/X11/driver-specific) | High | OPEN | *unassigned* | Compatibility matrix (Appendix A.3), recovery path (§30) | Field matrix (§64) passes on NVIDIA/AMD/Intel × Wayland/X11 | | R-09 | Low-end/resource-constrained device regression | High | OPEN | *unassigned* | L1/L2 budgets, resource-admission layer (§44.6.3) | Low-end qualification gate (§44.6.7) passes | | R-10 | Documentation drift (stale Tier-A docs more dangerous than missing ones) | High | MITIGATING | *unassigned* | Ownership manifest (`OWNERSHIP.yaml`) established Wave 0. Reconsidered at Wave 1 per this row's own exit condition: `docs:cef-check` remains deferred — Wave 1 produces TS contracts, not CEF/native code, so there is still nothing real for a drift-check to check drift against (same rationale `OWNERSHIP.yaml` already documents). Re-deferred to Wave 2, the first wave with actual CEF host code. | `docs:cef-check` implemented and green in CI | | R-11 | Feature-parity drift between Tauri and CEF during transition | High | OPEN | *unassigned* | Machine-readable parity ledger (§36 table + `tauri-coupling-inventory.json`) | Tauri retirement gate (§72) — full parity table PASS | | R-12 | Two-runtime maintenance burden destabilizes both | High | OPEN | *unassigned* | Short-lived parity period, Tauri feature freeze (§69) | CEF Stable cutover (Wave 19) | -| R-13 | Accessibility regression under CEF vs. current web/Tauri baseline | High | OPEN | *unassigned* | Early integration spike (§23.1) + deep certification wave (Wave 16) | Wave 16 exit criteria pass | +| R-13 | Accessibility regression under CEF vs. current web/Tauri baseline | High | MITIGATING | cef-runtime (backup: desktop-architecture) | Early integration spike (§23.1) + deep certification wave (Wave 16). Real evidence now exists: `CefBrowserHost::SetAccessibilityState(STATE_ENABLED)` proven in CI, 3/3 cycles, zero regression to FFI/rendering/crash-reporting/Wayland proofs (PR #397, after a real first-attempt failure correctly root-caused and reverted rather than left half-working — PR #391). | Wave 16 exit criteria pass — **not yet met**, reassessed 2026-08-19 only to record that the §23.1 spike half is real, not to claim Wave 16 certification. Platform accessibility-tree observability (AT-SPI) remains genuinely untouched — state *enablement* is proven, the tree itself is not, see `cef-architecture-primer.md`'s "Accessibility API" section. | | R-14 | Oversized bundled-Chromium footprint hurts low-end adoption | Medium/High | OPEN | *unassigned* | Low-end benchmark, lazy startup | Bake-off (§25) shows acceptable cold-start delta vs. Tauri on L1 | | **R-15** | **Desktop project-text-at-rest encryption gap** — Tauri filesystem-backed project stores (`services/fs/*Store.ts`) are not encrypted at rest; only the browser/PWA IndexedDB path is (ADR-0018/B-1). Formerly PR #356's scope; PR #363 (merged, v1.27.1) did not cover this — it addressed atomic writes and API-key routing only. | **High** | **OPEN** | *unassigned* | Rebuild on renderer-neutral `worldscript-crypto` (§20) with migration journal, admission lock, AAD, binary-asset coverage, recovery — same rigor as ADR-0018's IDB path. Do not patch the stale PR #356 implementation into the current architecture. | Wave 7 exit: desktop security claims truthful and tested (roadmap §71 Security gate) | | R-16 | Desktop credential storage remains OS-filesystem-based, not platform-keychain | Medium | OPEN | *unassigned* | PR #363 already fixed the immediate secret-material flaw (fail-closed routing, legacy key discard); full Keychain/Credential-Manager/Secret-Service integration deferred | Wave 7 exit: `worldscript-crypto`/credential storage matches §21 hierarchy | @@ -34,3 +34,5 @@ R-15–R-18 were derived directly from the Wave 0 PR reconciliation (roadmap §6 ## Review cadence This register should be reviewed at the exit of every Wave (roadmap §67) and whenever a new P0/P1-class finding surfaces. Owners are intentionally unassigned as of Wave 0 — assign before the corresponding wave begins, not before. + +**Wave 2 checkpoint (2026-08-19, via external review feedback on this session's own work):** R-06 and R-13 assigned real role-based owners (per `OWNERSHIP.yaml`'s established role taxonomy, roadmap §80.1.8 — role/subsystem ownership, not a named individual) and moved `OPEN` → `MITIGATING` with linked evidence, since their Wave-2-scoped mitigations genuinely exist now (learning harness, accessibility state enablement) — leaving them `*unassigned*`/`OPEN` had drifted behind the real implementation. The remaining rows (R-01–R-05, R-07–R-12, R-14) correctly stay `*unassigned*` per this section's own rule — their corresponding waves (5, 4, 12, 15, 16 field-matrix, etc.) have not begun. This is not a one-time fix: re-check at every future Wave exit, the same way this gap was caught. diff --git a/docs/cef/CEF-RUST-COMPETENCY-MATRIX.md b/docs/cef/CEF-RUST-COMPETENCY-MATRIX.md index f4d4570db..9a5ebaa57 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/#397/#400), 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/#400/#402), 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. @@ -13,7 +13,7 @@ cef_competency: lifetime_model_reviewed: true # docs/cef/knowledge/threading-and-lifetimes.md (PR #390) — UI-thread callbacks + ref-counting/callback-lifetime; IO thread and async cancellation still untouched 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 + sandbox_smoke: false # feasibility-only (PR #402): unshare --user --pid --fork functionally succeeds on the CI runner (kernel 6.17, unprivileged_userns_clone=1) — real evidence the sandbox is reachable, not a sandbox-enable attempt. Stays false until a follow-up PR proves browser/renderer/GPU processes actually run sandboxed (per-process status, not just "launched without complaining"), with zero regression to existing proofs — see cef-architecture-primer.md's "Sandbox configuration" section for the full acceptance bar 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: true # PR #400 — a self-induced crash inside our own code (rust-core's worldscript_rust_debug_crash_self_test, --debug-crash-self) was symbolized end-to-end via dump_syms + minidump-stackwalk (both standalone Rust tools, no Chromium checkout needed — that earlier assumption was wrong, see cef-architecture-primer.md). Chromium/CEF-internal frames remain unsymbolized — no distribution type ships a separate debug-symbols archive (verified against cef-builds.spotifycdn.com/index.json) — so this is honestly scoped to our own code, not the whole stack. ``` @@ -26,7 +26,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. | +| 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. Upgrade procedure written proactively (PR #402, `docs/cef/knowledge/binding-upgrade-playbook.md` — a real 15-step executable procedure, not a skeleton, but not yet exercised against a real upgrade); 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. 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: also proven now (PR #400) — the initial assumption that decoding a dump needs a full Chromium source checkout was wrong for *our own* code's frames; `dump_syms`/`minidump-stackwalk` (both standalone Rust projects, prebuilt Linux binaries, no Chromium checkout) resolved a self-induced browser-process crash (`--debug-crash-self`) end-to-end back to the crashing Rust function's name. Chromium/CEF-internal frames (e.g. the `chrome://crash` renderer crash above) remain genuinely unsymbolized — CEF's official builds ship no separate debug-symbols archive for any distribution type (verified against `cef-builds.spotifycdn.com/index.json`). Version-update automation and sandbox verification remain not started. | @@ -44,9 +44,10 @@ CI validation of this block ("fail CI when a required item for the active progra [x] Renderer crash observation green — PR #392, chrome://crash + OnRenderProcessTerminated (TS_PROCESS_CRASHED), browser process survived, cef-learning-harness CI job [ ] 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) [x] Crash-reporting/symbolization smoke green — PR #392 (crash reporting: real Crashpad dump produced in CI) + PR #400 (symbolization: a self-induced crash in our own code resolved end-to-end via dump_syms + minidump-stackwalk); Chromium/CEF-internal frames remain unsymbolized — 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] Linux dependency inventory (Wave 2 scope) complete — PR #395: package presence + real `ldd` against the CI-built runtime artifacts (`worldscript_host`, `libcef.so`), both fully resolved on the CI runner — matches this project's own established convention (see the X11/Wayland item below: proven on what Wave 2 actually needs, one CI runner, not blocked pending a broader matrix). Previously conflated with the separate item below; split out 2026-08-19 per the same "two separate gates" distinction just documented for sandbox. +[ ] Linux packaged-installer dependency declaration (multi-distro compatibility contract for an eventual real installer) — separate, later packaging-wave scope, not Wave 2; zero evidence, correctly unchecked; 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. -[ ] Upgrade playbook written +[x] Upgrade playbook written — PR #402: docs/cef/knowledge/binding-upgrade-playbook.md written proactively (a 15-step executable procedure mapping to real scripts/CI steps from Wave 2's own proof work), resolving the circular dependency where the gate required a playbook that could only be written after the first upgrade it was meant to gate. To be enriched with real lessons after the first actual upgrade — not yet exercised for real, honestly noted in the doc itself. [ ] External-expertise escalation path documented ``` @@ -60,14 +61,15 @@ CI validation of this block ("fail CI when a required item for the active progra [x] threading/lifetime map reviewed — PR #390, docs/cef/knowledge/threading-and-lifetimes.md (IO thread/async-cancellation still untouched — see domains table) [x] clean repeated startup/shutdown proven — PR #388, 3/3 cycles, cef-learning-harness CI job [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) +[x] sandbox development plan validated — PR #402: CEF has no Linux sandbox API (confirmed against docs/sandbox_setup.md); real functional feasibility test (unshare --user --pid --fork) succeeds on the CI runner; explicit acceptance bar documented for the follow-up enable attempt (cef-architecture-primer.md's "Sandbox configuration" section) — a validated plan, not yet the sandbox itself (sandbox_smoke stays false) +[x] Linux runtime dependencies inventoried (Wave 2 scope) — PR #395: package presence + real `ldd` against the CI-built runtime artifacts (`worldscript_host`, `libcef.so`), both fully resolved on the CI runner. Split 2026-08-19 from the packaged-installer item below — conflating Wave 2's own inventory goal with later packaging-wave scope was the same "two separate gates" issue just resolved for sandbox. +[ ] Linux packaged-installer dependency declaration (multi-distro compatibility contract for an eventual real installer — separate, later packaging-wave scope, not Wave 2; zero evidence, correctly unchecked; see native-readiness.md) [ ] 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); PR #400: symbolization also proven — a self-induced crash in our own code resolved end-to-end via dump_syms + minidump-stackwalk (Chromium/CEF-internal frames remain unsymbolized, honestly scoped) -[ ] upgrade playbook exists +[x] upgrade playbook exists — PR #402: docs/cef/knowledge/binding-upgrade-playbook.md, a real 15-step executable procedure, not a skeleton — see the Appendix A.1 entry above for the full rationale ``` -This gate is **not** satisfied yet — 8 of 12 items checked, several with explicit caveats above. `WS-CEF-IPC` (Wave 4) remains blocked. +This gate is **not** satisfied yet — 11 of 13 items checked (13, not 12 — the Linux dependency item was split into a Wave-2-scoped half now checked and a separate packaged-installer half, see below), several with explicit caveats above. `WS-CEF-IPC` (Wave 4) remains blocked. ## What this snapshot (Wave 2, 2026-08-18/19) does NOT claim @@ -76,7 +78,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 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). Crash symbolization is now proven for our own code's frames (PR #400) but not for Chromium/CEF-internal ones — see the domains table above. 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 (though PR #402 validated real feasibility for the follow-up enable attempt — see the domains table), and 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). Crash symbolization is now proven for our own code's frames (PR #400) but not for Chromium/CEF-internal ones. The upgrade playbook is no longer a skeleton either (PR #402, written proactively rather than waiting for a real upgrade — see the domains table). 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 66c2b1173..faeaf98ab 100644 --- a/docs/cef/OWNERSHIP.yaml +++ b/docs/cef/OWNERSHIP.yaml @@ -39,10 +39,11 @@ documents: backup_role: desktop-security last_verified: worldscript: "v1.27.1" - cef: "not applicable — pre-CEF-selection" + cef: "151.3.18+gbeff58d+chromium-151.0.7922.138" review_days: 90 related_ci: [] driftCheckTool: "planned — not implemented, see Wave 1" + note: "Wave 2 checkpoint (2026-08-19, external review feedback): R-06 (CEF/Rust/C++ lifetime defects) and R-13 (accessibility regression) had drifted — real Wave 2 implementation/CI evidence existed for both while they stayed *unassigned*/OPEN, past the point this register's own rule allows ('assign before the corresponding wave begins, not before' — Wave 2 has begun). Both assigned real role-based owners and moved to MITIGATING with linked evidence; the remaining rows correctly stay unassigned/OPEN since their waves haven't begun." - path: docs/cef/CEF-RUST-COMPETENCY-MATRIX.md tier: A @@ -56,7 +57,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/#397/#400) — 5 of 7 cef_competency items now true with linked evidence (lifetime_model_reviewed, renderer_crash_ci, crash_symbolization_smoke 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); crash_symbolization_smoke flipped true (PR #400 — self-induced crash in our own code resolved end-to-end via dump_syms + minidump-stackwalk, neither tool needs a Chromium checkout despite the earlier assumption; Chromium-internal frames remain genuinely unsymbolized, no distribution ships debug symbols); competency gate still not satisfied (8/12 — sandbox, accessibility-tree observability, Linux-inventory packaged-installer scope, upgrade playbook remain open)." + note: "Updated in place for Wave 2 (PR #386/#387/#388/#391/#392/#393/#397/#400/#402) — 5 of 7 cef_competency items now true with linked evidence (lifetime_model_reviewed, renderer_crash_ci, crash_symbolization_smoke 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); crash_symbolization_smoke flipped true (PR #400 — self-induced crash in our own code resolved end-to-end via dump_syms + minidump-stackwalk, neither tool needs a Chromium checkout despite the earlier assumption; Chromium-internal frames remain genuinely unsymbolized, no distribution ships debug symbols); PR #402: sandbox development plan validated (real feasibility test succeeds on the CI runner, explicit acceptance bar documented — sandbox_smoke itself stays false); the Linux-dependency gate item split into a Wave-2-scoped half (now checked — PR #395's inventory+ldd work was already complete, just conflated with a different later goal) and a separate packaged-installer half (stays unchecked, correctly later scope); and binding-upgrade-playbook.md written proactively with a real 15-step executable procedure (resolving a circular dependency — the gate required a playbook that could previously only be written after the first upgrade it was meant to gate), so 'upgrade playbook exists'/'Upgrade playbook written' both flip to checked — competency gate still not satisfied (11/13, was 8/12 before the split; sandbox_smoke, accessibility-tree observability, Linux packaged-installer declaration remain open)." - path: docs/cef/TAURI-COUPLING-INVENTORY.md tier: B @@ -173,11 +174,14 @@ documents: tier: A owner_role: rust-core backup_role: cef-runtime - last_verified: null + last_verified: + worldscript: "v1.27.1" + cef: "151.3.18+gbeff58d+chromium-151.0.7922.138" review_days: 90 - related_ci: [] + related_ci: + - cef-learning-harness driftCheckTool: "planned — not implemented, see Wave 1" - note: "Skeleton only — Status: Not started" + note: "Written proactively (PR #402), not left blank pending the first real upgrade — resolves a circular dependency (the competency gate required this doc to exist before Wave 4, but it could previously only be written after the first upgrade it was meant to gate). 15-step executable procedure mapping to real scripts/CI steps from Wave 2's own proof work (SDK pin/fetch/verify, build, lifecycle harness, sandbox, crash, symbolization, accessibility, X11, Wayland, dependency/linkage diff, docs, rollback). Not yet exercised against a real upgrade — must be enriched with what actually broke the first time one happens, honestly noted in the doc itself." - path: docs/cef/UI-DOMAIN-STATE-CLASSIFICATION.md tier: B diff --git a/docs/cef/knowledge/binding-upgrade-playbook.md b/docs/cef/knowledge/binding-upgrade-playbook.md index d09f5382c..22f7974ae 100644 --- a/docs/cef/knowledge/binding-upgrade-playbook.md +++ b/docs/cef/knowledge/binding-upgrade-playbook.md @@ -1,18 +1,40 @@ # CEF/Binding Upgrade Playbook -**Status:** Not started — this playbook's own prose is unwritten because no CEF/Chromium version upgrade has ever happened yet in this repository. CEF integration itself is real (`apps/desktop-cef/`, ADR-0020), and a version is pinned (`scripts/cef/cef-version.json`, currently `151.3.18+gbeff58d+chromium-151.0.7922.138`) — this doc will get real content the first time that pin actually moves. +**Status:** Written proactively (2026-08-19), before any real CEF/Chromium version upgrade has happened — not left blank waiting for one. The competency gate (`docs/cef/CEF-RUST-COMPETENCY-MATRIX.md`) requires "upgrade playbook exists" before Wave 4's privileged IPC proceeds; leaving this genuinely empty until the first upgrade would create a circular dependency (playbook can't exist until an upgrade happens, but nothing forces an upgrade to happen). Every step below maps to a real script or CI job that already exists in this repo from Wave 2's own proof work — this is an executable checklist synthesized from real evidence, not a hypothetical. **After the first real upgrade, enrich this with what actually broke and what the checklist missed** — that update is still required and this document is not "done" just because it has content now. **Scope:** The repeatable procedure for upgrading the pinned CEF/Chromium version and/or the Rust binding crate version — what to re-validate, what evidence to collect, and how the competency-regression policy (roadmap §61.1.6) applies. **Tier:** A (release/security-critical) — see [`../OWNERSHIP.yaml`](../OWNERSHIP.yaml). **Roadmap context:** [`../ROADMAP-CEF-DESKTOP-MIGRATION.md`](../ROADMAP-CEF-DESKTOP-MIGRATION.md) §34 (CEF version governance), §34.1 (security patch SLA), §44.4 (compatibility-floor gate), §61.1.6 (competency regression policy). -## Outline (to be filled in once a first CEF version upgrade actually happens) +## Routine upgrade procedure -- Routine upgrade checklist: did Linux runtime dependencies change? did the distro/glibc floor move? did Wayland/X11 behavior change? did sandbox requirements change? did packaged resource layout change? (§44.4) -- Emergency security-patch upgrade path — shortened validation, but never skipping signature verification, sandbox smoke, startup, project open/save, updater correctness, basic a11y/focus, migration compatibility (§34.1.2) -- Required re-run of the learning harness (§61.1) before any upgrade merges — an upgrade that breaks the harness is not merge-eligible until fixed or the harness is deliberately updated (§61.1.6) -- Binding-crate upgrade specifics (API surface diffs, new unsafe boundaries, changelog review) -- Last-known-good runtime retention for rollback (Appendix M.1) +Every step in this chain is a real command or CI step that already exists — this is not aspirational. + +1. **Change the pin.** Edit `scripts/cef/cef-version.json` only (`cefVersion`, `filename`, `sha1`, `sizeBytes`) — per that file's own `$comment`, values come straight from `https://cef-builds.spotifycdn.com/index.json`, never hand-computed. This is deliberately the *only version-pin file* a routine version bump touches (step 14 below still updates docs in the same PR — "only file" refers to the pin itself, not the complete procedure); `scripts/cef/fetch-cef-sdk.mjs` reads it, nothing hardcodes a version. +2. **Verify checksum integrity before trusting the new pin.** `node scripts/cef/fetch-cef-sdk.mjs --cache-dir .cef-cache` downloads and compares the archive's SHA-1 against the pinned `sha1` — a mismatch deletes the bad download and fails loudly (see `verifyArchive()`), it does not silently proceed. This is a checksum/integrity check against a value this repo itself maintains, not a cryptographic signature or provenance verification (no independent trust anchor is involved) — `verifyArchive()` does not check `sizeBytes`, only `sha1`. +3. **Fetch.** Same command as step 2 — idempotent, safe to re-run. +4. **Build.** `cmake -S -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build --target worldscript_host --parallel $(nproc)` (`.github/workflows/cef-learning-harness.yml`'s "Configure + build worldscript_host" step). A build failure here is the first, cheapest signal of an API-incompatible upgrade — cheaper than discovering it at runtime. +5. **Lifecycle harness.** `xvfb-run -a node scripts/cef/run-launch-cycle-proof.mjs --cycles 3` — repeated start/close cycles, FFI boundary (`rust_core ping = 424242`), real rendering (exact title check), accessibility-state request. Per roadmap §61.1.6, **a CEF upgrade that breaks this harness is automatically not merge-eligible** until fixed or the harness is deliberately updated with documented, understood upstream-behavior changes — never silently. +6. **Sandbox.** `node scripts/cef/check-linux-sandbox-inventory.mjs` (diagnostic feasibility today; once the follow-up sandbox-enable PR lands, this step becomes a real sandbox-status proof, not just a feasibility check — see `cef-architecture-primer.md`'s "Sandbox configuration" section for that PR's own acceptance bar). Re-run regardless of upgrade type — an upstream Chromium change can alter sandbox requirements (roadmap §44.4 asks this explicitly). +7. **Crash reporting.** The launch-cycle harness's own `runCrashReportingProofCycle()` (same script, step 5) — `chrome://crash`, `CefCrashReportingEnabled()`, `OnRenderProcessTerminated` with `TS_PROCESS_CRASHED`, a real Crashpad `.dmp` file under `BREAKPAD_DUMP_LOCATION`. +8. **Symbolization.** `xvfb-run -a node scripts/cef/run-symbolization-proof.mjs ` — `--debug-crash-self`, SIGABRT, dump, `dump_syms -s`, `minidump-stackwalk --json`, `crashing_thread.frames[].function` resolves. Requires `worldscript_host` built with `-g` (`apps/desktop-cef/CMakeLists.txt`'s `target_compile_options`) — do not accidentally drop that flag while chasing an upgrade-induced build fix. +9. **Accessibility-state enablement.** Covered by step 5's `accessibility_state_requested = true` check — this asserts *state enablement* only (`SetAccessibilityState(STATE_ENABLED)` was requested), not focus behavior or platform accessibility-tree observability (AT-SPI); see the competency matrix for why the tree/focus half is separate, unattempted scope. Do not read this step as a "basic accessibility/focus" check without extending it with an explicit focus assertion first. +10. **X11.** Covered by step 5 — the harness runs under `xvfb-run` (X11) by default. +11. **Wayland.** `node scripts/cef/run-wayland-smoke.mjs ` against a headless Weston compositor (`--ozone-platform=wayland`) — same FFI+title checks as X11. Non-blocking in CI (`continue-on-error: true`) but still run and read, not skipped. +12. **Dependency/linkage diff.** `node scripts/cef/check-linux-runtime-deps.mjs` (dpkg package presence — did the required package list change?) and `node scripts/cef/check-linux-runtime-linkage.mjs build/worldscript_host` (real `ldd` against the newly-built artifacts — did a new unresolved shared-library dependency appear?). Directly answers roadmap §44.4's "did required Linux runtime dependencies change?" and "did packaged resource layout change?" with real command output, not a changelog read. +13. **API diff.** No automated tooling for this yet (real gap, not hidden) — manually diff the new CEF version's `include/` headers against the pinned one (or read CEF's own release notes at `https://cef-builds.spotifycdn.com` / the `chromiumembedded/cef` GitHub mirror's tag-to-tag diff) for signature changes to any API this repo actually calls: `CefExecuteProcess`, `CefInitialize`, `CefSettings`, `CefBrowserView`/`CefWindow` creation, `CefClient`/`CefRequestHandler`/`OnRenderProcessTerminated`, `CefBrowserHost::SetAccessibilityState`, `CefCrashReportingEnabled`, `cef_crash_util.h`. A build failure at step 4 will catch most of these mechanically; this step is for behavioral/semantic changes a successful compile wouldn't surface. +14. **Docs.** Update in the same PR, not a follow-up: `scripts/cef/cef-version.json`'s pin (step 1, already done), `docs/cef/CEF-RUST-COMPETENCY-MATRIX.md`'s `last_verified.cef` fields (`CEF-RUST-COMPETENCY-MATRIX.md`, `CEF-BINDING-DECISION-SCORECARD.md`, `cef-architecture-primer.md`, `cef-rust-binding-cookbook.md`, `threading-and-lifetimes.md`, `linux-runtime-notes.md` per `docs/cef/OWNERSHIP.yaml`'s per-doc `last_verified.cef` field), and this playbook itself with what was actually re-validated and what broke (see "This is a living procedure" below). +15. **Rollback.** Scope depends on what actually changed — reverting only `cef-version.json` does not undo a `Cargo.toml`/`CMakeLists.txt` change, and doing so would leave an incompatible partial upgrade in the branch: + - **CEF-only upgrade** (just `scripts/cef/cef-version.json` changed, per step 1): revert that one file to the last-known-good values (roadmap Appendix M.1's "last-known-good runtime retention for rollback") — genuinely a one-file revert, since step 1 deliberately keeps the pin isolated from everything else for exactly this case. + - **Rust binding or Corrosion upgrade** (`apps/desktop-cef/rust-core/Cargo.toml`'s `edition`, or `apps/desktop-cef/CMakeLists.txt`'s Corrosion `GIT_TAG`, also changed — see "Binding-crate upgrade specifics" below): make the whole upgrade one atomic commit in the first place, so rollback is `git revert `, not a partial file-by-file undo. If the upgrade was already split across multiple commits before a failure was discovered, revert all of them together, not just the pin. + +## Emergency security-patch path (roadmap §34.1.2) + +Shortened, but never omitting: checksum/integrity verification (step 2 — note: this is a SHA-1 comparison against a value this repo itself maintains, not cryptographic signature or provenance verification against an independent trust anchor; the roadmap's own §34.1.2 wording says "signature verification," and that gap between the roadmap's stated requirement and what's actually implemented is real and still open, not silently assumed closed by step 2), sandbox smoke (step 6), application startup (step 5), project open/save (not yet CEF-side — Wave 5+ scope, N/A until then), updater correctness (N/A — no CEF-side updater exists yet), accessibility-state enablement (step 9 — state only, not focus), migration compatibility (N/A until Wave 5+ persistence work lands). May skip: Wayland (step 11, already non-blocking), the full API-diff read (step 13) in favor of a targeted read of just the CVE's affected code paths, and broader distro/hardware validation (roadmap §44.5/§64, already out of this project's CI scope entirely). + +## Binding-crate (Rust/`worldscript_rust_core`) upgrade specifics + +`apps/desktop-cef/rust-core/Cargo.toml` pins `edition = "2021"` and Corrosion (`v0.5.1` in `apps/desktop-cef/CMakeLists.txt`'s `FetchContent_Declare`) — a version bump to either needs the same step-4 build-first signal, plus checking `panic = "abort"` and `debug = true` (`[profile.release]`) both survive the bump (the former is load-bearing for the crash-reporting/symbolization proofs at steps 7–8; the latter is load-bearing for step 8 specifically). Corrosion version bumps separately: re-run step 4 with a clean `build/` directory (stale Corrosion-generated CMake cache files are a plausible false-failure source, not a real incompatibility) before concluding something broke. **Make the bump one atomic commit** (unlike step 1's isolated one-file CEF pin change, a binding/Corrosion bump touches `Cargo.toml`/`CMakeLists.txt` together) — this is what makes step 15's rollback guidance for this case (`git revert` the one commit) actually work. ## This is a living procedure -Update this playbook every time a real upgrade happens, with what was actually re-validated and what broke — not a hypothetical checklist frozen at Wave 0. +Update this playbook every time a real upgrade happens, with what was actually re-validated and what broke — the numbered steps above are what Wave 2's own proof work makes possible *today*; a real upgrade will surface gaps this checklist doesn't yet know to ask about. Do not let this document freeze back into a hypothetical once it has been exercised for real. diff --git a/docs/cef/knowledge/cef-architecture-primer.md b/docs/cef/knowledge/cef-architecture-primer.md index 36ab14f72..6d5b68240 100644 --- a/docs/cef/knowledge/cef-architecture-primer.md +++ b/docs/cef/knowledge/cef-architecture-primer.md @@ -75,6 +75,14 @@ Roadmap §44.2 is explicit: *"'CEF uses Chromium' is not accepted as proof of Wa `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. +**Confirmed against CEF's own `docs/sandbox_setup.md` before writing any code (PR #402)**: unlike Windows (`cef_sandbox_win.h`, static-library linking) and macOS (`cef_sandbox_mac.h`, `dlopen`'d dylib), CEF has **no Linux-specific sandbox API at all**. The doc's entire Linux section is one line pointing at Chromium's own `docs/linux_sandboxing.md`: the sandbox is a Chromium-internal mechanism, `CefSettings.no_sandbox` the only lever. Layer-1 (process/namespace isolation) uses either the legacy setuid `chrome-sandbox` helper (root-owned, setuid bit) or — automatically preferred since Chromium M-43 if the kernel/policy allows it — unprivileged user namespaces, no setuid binary needed. Layer-2 (seccomp-bpf syscall filtering) is independent of layer-1. + +**Diagnostic-only feasibility check (PR #402)**: `scripts/cef/check-linux-sandbox-inventory.mjs` ran a *functional* test (`unshare --user --pid --fork`, not just reading the `kernel.unprivileged_userns_clone` sysctl — a container/AppArmor restriction can block namespace creation even when the sysctl claims it's allowed) on the CI runner. Real result: kernel `6.17.0-1022-azure`, sysctl `1`, AppArmor module enabled, and `unshare` **succeeded** — unprivileged user-namespace sandboxing appears reachable here. This is evidence the follow-up attempt has a real chance, not a guarantee it will work end-to-end once CEF's own zygote/GPU process spawning is actually exercised under it. + +**Acceptance bar for the follow-up enable attempt (not yet done — separate PR)**: not just "CEF starts without `no_sandbox=true`." Must show browser, renderer, and utility/GPU processes actually running under the sandbox — Chromium's own guidance is to check each process's *real* sandbox status (e.g. `chrome://sandbox`, or an equivalent CI-observable signal), since Linux combines namespace isolation *and* seccomp-BPF and both matter independently. Zero regression to the existing lifecycle/crash-reporting/symbolization/accessibility-state/Wayland proofs. A reproducible sandbox-status proof in CI, not a one-off manual check. **Explicitly disallowed**: silently trading `no_sandbox=true` for a narrower blanket disable (e.g. `--disable-setuid-sandbox`) to get past a launch failure while still claiming this row proven — that would misrepresent what's actually protected, the same honesty standard already applied to the accessibility and symbolization sections above. + +**Two separate gates, not one**: a GitHub Actions runner can prove "our CEF configuration is *capable* of running sandboxed" (a CI-sandbox-proof) — it cannot prove "our eventual `.deb`/AppImage/installer distribution correctly installs the sandbox helper, its permissions, and the runtime layout on every target Linux distribution" (a production-packaging-sandbox-proof). The latter is real, separate, later packaging-wave scope (matching the roadmap's existing treatment of installer packaging elsewhere in this doc) — not to be pulled forward into Wave 2 just because a CI proof exists. + ## Process tree — what we can honestly claim ```text diff --git a/scripts/cef/check-linux-sandbox-inventory.mjs b/scripts/cef/check-linux-sandbox-inventory.mjs new file mode 100644 index 000000000..19e67be2b --- /dev/null +++ b/scripts/cef/check-linux-sandbox-inventory.mjs @@ -0,0 +1,82 @@ +#!/usr/bin/env node +/** + * CEF Linux sandbox feasibility inventory (Wave 2, roadmap Sandbox posture item — "Not yet + * attempted" in docs/architecture/native-readiness.md). + * + * Diagnostic-only, non-invasive — reports real evidence about whether this runner can support + * Chromium's Linux sandbox, before any attempt to actually enable it (CefSettings.no_sandbox is + * currently `true` unconditionally, apps/desktop-cef/src/main.cpp, ADR-0020). Never changes + * worldscript_host's build or runtime behavior. + * + * Background (confirmed against CEF's own docs/sandbox_setup.md and Chromium's + * docs/linux_sandboxing.md before writing this — CEF has no Linux-specific sandbox API, unlike + * cef_sandbox_win.h/cef_sandbox_mac.h; the sandbox is entirely a Chromium-internal mechanism, + * toggled only via CefSettings.no_sandbox): + * - Layer-1 (process/namespace isolation): the legacy setuid `chrome-sandbox` helper (needs + * root ownership + the setuid bit), or the modern unprivileged user-namespaces sandbox + * (preferred automatically since Chromium M-43 if the kernel/policy allows it — no setuid + * binary needed at all). + * - Layer-2 (seccomp-bpf syscall filtering): independent of layer-1, needs Linux kernel >= 3.5. + * + * This script checks real, functional evidence for layer-1 feasibility — not just documentation + * claims — since a sysctl value alone doesn't prove unprivileged namespace creation actually + * succeeds (AppArmor profiles or container restrictions can block it even when the sysctl says + * "enabled"). + * + * Run: node scripts/cef/check-linux-sandbox-inventory.mjs + */ +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; + +function tryRun(cmd, args) { + try { + return execFileSync(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] }) + .toString() + .trim(); + } catch { + return null; + } +} + +console.log('[check-sandbox-inventory] CEF Linux sandbox feasibility inventory:'); + +const kernelRelease = tryRun('uname', ['-r']); +console.log(` kernel release: ${kernelRelease ?? '(uname failed)'}`); + +// QNBS-v3: this sysctl is a Debian/Ubuntu-specific patch, not present on every distro/kernel — +// its absence is itself informative (means the mainline kernel default applies), not an error. +let sysctlValue = null; +try { + sysctlValue = fs.readFileSync('/proc/sys/kernel/unprivileged_userns_clone', 'utf8').trim(); + console.log(` kernel.unprivileged_userns_clone: ${sysctlValue}`); +} catch { + console.log(' kernel.unprivileged_userns_clone: (sysctl not present on this kernel/distro)'); +} + +// QNBS-v3: functional test, not just reading the sysctl — AppArmor profiles or container-level +// restrictions (e.g. missing CAP_SYS_ADMIN, seccomp policies on the runner itself) can block +// unprivileged namespace creation even when the sysctl claims it's allowed. +const unshareTest = tryRun('unshare', ['--user', '--pid', '--fork', 'true']); +const unshareWorks = unshareTest !== null; +console.log( + ` unshare --user --pid --fork (functional test): ${unshareWorks ? 'succeeded' : 'FAILED'}`, +); + +// QNBS-v3: existsSync is not a guarantee the following read succeeds (TOCTOU, permissions) — +// an unguarded readFileSync throwing here would abort this entire diagnostic script before it +// even reaches CEF SDK setup. CodeRabbit finding on PR #402. +let aaEnabled = null; +if (fs.existsSync('/sys/module/apparmor/parameters/enabled')) { + try { + aaEnabled = fs.readFileSync('/sys/module/apparmor/parameters/enabled', 'utf8').trim(); + } catch { + aaEnabled = '(read failed)'; + } +} +console.log(` AppArmor module enabled: ${aaEnabled ?? '(not present)'}`); + +console.log( + `\n[check-sandbox-inventory] Verdict: unprivileged user-namespace sandboxing appears ` + + `${unshareWorks ? 'FEASIBLE' : 'NOT FEASIBLE'} on this runner (functional test, not just a sysctl read). ` + + `This is informational only — no sandbox behavior was changed by running this script.`, +);