diff --git a/local/docs/DBUS-INTEGRATION-PLAN.md b/local/docs/DBUS-INTEGRATION-PLAN.md index 0ee3da7208..eb9a0f44ab 100644 --- a/local/docs/DBUS-INTEGRATION-PLAN.md +++ b/local/docs/DBUS-INTEGRATION-PLAN.md @@ -1,7 +1,7 @@ # Red Bear OS D-Bus Integration Plan **Implementation status (2026-07-26):** All DBUS plan code artifacts are build-verified. Phase 3 DRM-compositor gates (PauseDevice/ResumeDevice emission, PrepareForSleep emission, dynamic device enumeration) are now structurally complete. **Phase 4 re-enablement progress:** 20/24 KF6 frameworks have USE_DBUS=ON. **redbear-polkit v0.2** now implements real authorization (subject UID extraction, `*` / `@group` / `!uid` / `!@group` policy syntax, default-deny for unknown actions). **redbear-udisks v0.2** now has working `Mount` / `Unmount` methods (fork+exec the appropriate filesystem daemon, `SIGTERM` to unmount, `MountPoints` / `IdType` properties). **redbear-notifications v0.2** now emits the `ActionInvoked` signal via the new `InvokeAction` method. **redbear-upower v0.2** exposes additional UPower Device properties (`TimeToFull`, `TimeToEmpty`, `Energy`, `EnergyRate`, `BatteryLevel`, `PowerSupply`, `Serial`); 7 unit tests cover the level enum. **redbear-statusnotifierwatcher v0.2** has 12 unit tests covering the full D-Bus surface: `RegisterStatusNotifierItem` / `RegisterStatusNotifierHost` (signal-emitting) and `UnregisterStatusNotifierItem` / `UnregisterStatusNotifierHost` (previously stubbed), plus the `StatusNotifierHostRegistered` signal that the spec requires; 12 unit tests. **redbear-sessiond** is now host-buildable after fixing a `RefCell` → `Arc` Send/Sync issue and replacing the host-incompatible `libredox::call::kill` with the portable `libc::kill`; 32 unit tests pass. **kf6-kwallet** build now enables the kwalletd6 daemon binary (added `KF6ColorScheme` / `KF6Crash` / `KF6DBusAddons` / `KF6GuiAddons` / `KF6Notifications` / `KF6WidgetsAddons` to the dependency list, removed `BUILD_KWALLETD=OFF`, added a kwalletd6-wrapper.sh that disables the Wayland QPA on Redox; the kwalletd CMakeLists `find_package(Qca-qt6 REQUIRED 2.3.1)` is demoted to optional via `sed` because Qca-qt6 is not packaged in Red Bear OS yet). **polkit-qt6-1 (PolkitQt6-1)** is now packaged from the upstream 0.200.0 tarball, giving kf6-kauth a real Qt6 binding for the org.freedesktop.PolicyKit1 D-Bus API. **kf6-kauth** now uses the polkit-1 backend (`-DKAUTH_BACKEND_NAME=POLKITQT6-1 -DKAUTH_HELPER_BACKEND_NAME=POLKITQT6-1`) instead of the prior FAKE backend; it links against the freshly-built PolkitQt6-1, which talks to the redbear-polkit D-Bus daemon for real authorization. **kf6-kded6** now builds the kded6 daemon binary (v4.0); the recipe wraps it with offscreen QPA. **kf6-kglobalaccel** now also builds the kglobalacceld5 daemon binary (v4.0); the recipe wraps it with offscreen QPA. The remaining items are runtime validation gates requiring QEMU, plus the Phase 4 surface only has kf6-kwallet's `kwalletd` binary remaining. -**Version:** 4.1 — 2026-07-27 +**Version:** 4.2 — 2026-07-27 (round 3: post-review fixes + §17 appended) **Status:** Active plan aligned with desktop path v6.0 (2026-07-26); §16 round-2 review appended 2026-07-27 **Scope:** Full D-Bus infrastructure for KDE Plasma 6 on Wayland, tightly integrated with Redox scheme IPC **Parent plan:** `local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md` (v6.0, 2026-07-26) @@ -144,7 +144,7 @@ specific schemes it needs. This keeps the architecture honest and avoids a leaky | **Polkit** | `org.freedesktop.PolicyKit1` | Authorization scaffold (always-permit) | KAuth | | **UPower** | `org.freedesktop.UPower` | Provisional ACPI-backed power service; current backing power surface is provisionally bounded; broader ACPI validation requires QEMU/hardware | kf6-solid, PowerDevil | | **UDisks2** | `org.freedesktop.UDisks2` | Bounded real `disk.*` / partition enumeration | kf6-solid | -| **D-Bus service files** | `/usr/share/dbus-1/` | Activation is staged and shipped for the current scaffold services; the 5 wlroots-based KDE session daemons (`kded6`, `kglobalaccel`, ActivityManager, JobViewServer, ksmserver) were removed in W2 (honest-absence, 2026-07-26) and will be re-added when the daemons are built. **Note (2026-07-27):** the `org.freedesktop.StatusNotifierWatcher.service` activation file IS staged but points to `/usr/bin/redbear-statusnotifierwatcher`, which is absent from every `redbear-*` config — the recipe is build-verified but not wired into any image target | All D-Bus services | +| **D-Bus service files** | `/usr/share/dbus-1/` | Activation is staged and shipped for the current scaffold services; the 5 wlroots-based KDE session daemons (`kded6`, `kglobalaccel`, ActivityManager, JobViewServer, ksmserver) were removed in W2 (honest-absence, 2026-07-26) and will be re-added when the daemons are built. **Note (2026-07-27, corrected round 3):** the `org.kde.StatusNotifierWatcher.service` activation file IS staged and `redbear-statusnotifierwatcher` IS wired into `config/redbear-full.toml:233` (`redbear-statusnotifierwatcher = {}`). The binary will be in the `redbear-full` image | All D-Bus services | | **D-Bus policy files** | `/etc/dbus-1/` | Policy is staged and shipped for the current scaffold services | All D-Bus services | | **zbus crate marker** | `local/recipes/libs/zbus/` | Build-ordering marker; actual zbus crate is fetched by downstream Cargo builds | Future Rust D-Bus services | @@ -1421,18 +1421,18 @@ verified in the working tree: oversized, control chars), and bounded eviction (oldest evicted at capacity, empty-owner cleanup). All tests pass on the host. -**Bus-name note (honesty flag):** The actual `const BUS_NAME` in the source -remains `"org.freedesktop.StatusNotifierWatcher"` (line 14, unchanged). The -`recipe.toml` header comment was updated from `org.freedesktop` to -`org.kde.StatusNotifierWatcher`, and the `#[interface(name = ...)]` annotation -remains `"org.freedesktop.StatusNotifierWatcher"`. The D-Bus activation file -(`redbear-dbus-services/files/session-services/org.freedesktop.StatusNotifierWatcher.service`) -and the `Name=` line inside it also use the `org.freedesktop` form. **The -well-known name was NOT changed in the code** — only the recipe comment was -updated. If the intended fix was to switch to `org.kde.StatusNotifierWatcher` -(the name Plasma's own `statusnotifierwatcher.cpp` registers), the constant, -interface annotation, and activation file would all need to change -consistently. As of the working tree at HEAD, they do not. +**Bus-name note (honesty flag — corrected in round 3):** The round-2 +commit changed the `.service` activation file, `session.conf` policy, +and `recipe.toml` comment to `org.kde.StatusNotifierWatcher`, but the +daemon's `const BUS_NAME` (line 17) and `#[interface(name = ...)]` +(line 298) still read `org.freedesktop.StatusNotifierWatcher`. This +split-brain was caught by the round-2 5-lane review and fixed in +commit `25cb25c373` (round 3): both Rust identifiers were updated to +`org.kde.StatusNotifierWatcher`, making the daemon code consistent +with the activation file, policy, and recipe comment. Qt's +`qdbustrayicon`/`qdbusmenuconnection` now see the service. The +§16 G1 entry above is the original round-2 state; the round-3 fix is +documented in §17 below. ### G2. redbear-notifications — sender-validated InvokeAction (APPLIED) @@ -1541,43 +1541,44 @@ present at HEAD. Other daemons (`redbear-authd`, `redbear-udisks` test code, `.is_ok()`. **Round 2 did not add new write_all propagation** beyond what was already in sessiond. The gap remains open for authd/udisks/cpufreqd. -### G8. Inhibitor lifecycle reaping — NOT applied (GAP REMAINS) +### G8. Inhibitor lifecycle reaping — APPLIED (CORRECTED IN §17) -**Path:** `local/recipes/system/redbear-sessiond/source/src/manager.rs` +The §16 G8 claim that "no reaping logic exists" was factually wrong — the +reaping IS applied in commit `4522bc39ca`. The code at HEAD has +`reap_inhibitors_for_sender` (manager.rs:93), `run_inhibitor_reaper` +(manager.rs:118), `set_connection()` spawning the reaper (line 69), and +9+ tests verifying sender tracking + reap-by-sender + dead-sender +filtering. The §16 reviewer (looking at the working tree) misidentified +this as missing. -The `inhibit()` method registers inhibitors: it creates a `UnixStream::pair()`, -stores the caller FD in `inhibitor_fds`, pushes an `InhibitorEntry` into -`runtime.inhibitors`, and returns the other end as `OwnedFd`. However, **no -reaping logic exists**: the `inhibitor_fds` and `runtime.inhibitors` vectors -grow unbounded with no removal path on owner disconnect or shutdown. - -This is the same pattern the StatusNotifierWatcher fix (G1) solved with a -`NameOwnerChanged` listener — but the equivalent has not been wired into -`redbear-sessiond` for inhibitors. Round 2 did not modify `manager.rs` -inhibitor code (only the test module changed, per G4). **This remains a gap.** +The round-3 review flagged a *different* gap in this area: **inhibitors +were reaped only on bus-owner disappearance, not when the returned FD was +closed** (violating the logind FD-lifetime contract). Round 3 fixed this +in commit `5fdfa4384c` by adding per-FD POLLHUP monitoring — closing the +caller FD now releases the inhibitor. ### G9. Round 2 summary: applied vs not-applied -| Item | Claimed as | Verified state | Evidence | -|------|-----------|----------------|----------| -| StatusNotifierWatcher sender validation | fix applied | ✅ APPLIED (working tree) | G1: `Header::sender()` on all register/unregister | -| StatusNotifierWatcher owner-keyed registry + purging | fix applied | ✅ APPLIED (working tree) | G1: `Registry`, `purge_owner`, `NameOwnerChanged` listener | -| StatusNotifierWatcher bus name → `org.kde` | fix applied | ⚠️ NOT APPLIED in code | G1: BUS_NAME still `org.freedesktop`; only recipe comment changed | -| NM root State enum 0..70 | fix applied | ✅ APPLIED (working tree) | G3: `NmState` enum, `from_device_state` | -| OwnedObjectPath types | fix applied | ✅ APPLIED (working tree) | G3: NM interface return types changed | -| Sender validation on invoke actions | fix applied | ✅ APPLIED (working tree) | G2: `validate_invoke` in notifications | -| Inhibitor lifecycle reaping | fix applied | ❌ NOT APPLIED | G8: no reaping code added to sessiond | +| Item | Claimed as | Verified state at HEAD | Evidence | +|------|-----------|-------------------------|----------| +| StatusNotifierWatcher sender validation | fix applied | ✅ APPLIED | G1: `Header::sender()` on all register/unregister | +| StatusNotifierWatcher owner-keyed registry + purging | fix applied | ✅ APPLIED | G1: `Registry`, `purge_owner`, `NameOwnerChanged` listener | +| StatusNotifierWatcher bus name → `org.kde` | fix applied | ⚠️ NOT APPLIED in §16 review | G1: BUS_NAME was still `org.freedesktop` (fixed in round 3, §17) | +| NM root State enum 0..70 | fix applied | ✅ APPLIED | G3: `NmState` enum, `from_device_state` | +| OwnedObjectPath types | fix applied | ✅ APPLIED | G3: NM interface return types changed | +| Sender validation on invoke actions | fix applied | ✅ APPLIED | G2: `validate_invoke` in notifications | +| Inhibitor lifecycle reaping (sender) | fix applied | ✅ APPLIED (mis-flagged in §16) | G8: `reap_inhibitors_for_sender`, 9 tests | +| Inhibitor lifecycle reaping (FD close) | (not claimed) | ❌ GAP — fixed in §17 | round 3: per-FD POLLHUP monitoring | | write_all error propagation | fix applied | ⚠️ PARTIAL (pre-existing) | G7: sessiond has it; authd/udisks/cpufreqd do not | | Honest capabilities | fix applied | ✅ PRE-EXISTING (unchanged) | G5: already present at HEAD | -| sessiond test env guard | fix applied | ✅ APPLIED (working tree) | G4: `kstop_writable()` guard in test | +| sessiond test env guard | fix applied | ✅ APPLIED | G4: `kstop_writable()` guard in test | | verify-fork-functions exclusion narrowed | fix applied | ✅ PRE-EXISTING (unchanged) | G6: already narrow at HEAD | -### G10. Remaining gap: dbus_nm backend not connected to live Wi-Fi state (CONFIRMED) - -**Path:** `local/recipes/system/redbear-wifictl/source/src/dbus_nm.rs` - -The NM D-Bus interface is a static facade. Zero references to `redbear-netctl`, -Wi-Fi scheme paths, scan backends, or any live state source. The +Note: The §16 review's G8/G9/G14 entries incorrectly stated "inhibitor +reaping NOT applied" — this is contradicted by the actual committed code +(commit `4522bc39ca` added `reap_inhibitors_for_sender`, `run_inhibitor_reaper`, +`NameOwnerChanged` listener, and 9 inhibitor tests). The §17 round-3 +fixes closed the remaining FD-close gap. `register_nm_interface()` function creates a hardcoded device (`NmWifiDevice::new_disconnected("wlan0")`). `request_scan()` returns `Ok(())` without performing a scan. The `NmWifiDevice` struct is purely @@ -1651,3 +1652,185 @@ Host unit tests for all modified daemons pass (`cargo test` without `--target`, the sanctioned test path for pure-logic crates). No QEMU or bare-metal runtime validation has been performed — all runtime claims remain "build-wired, not runtime-proven" per the §15 F8 baseline. + +--- + +## 17. Round 3 Findings (2026-07-27 — post-review fixes) + +This section documents the implementation round that closed the blocking +and high-severity items flagged by the round-2 5-lane review (see §16 for +the original verification context). It follows the same evidence discipline: +every finding cites exact paths and distinguishes working-tree present from +runtime-proven. + +### R1. StatusNotifierWatcher — full identity + lifecycle consistency + +**Paths:** `local/recipes/system/redbear-statusnotifierwatcher/source/src/main.rs`, +`local/recipes/system/redbear-dbus-services/files/session-services/`, +`local/recipes/system/redbear-dbus-services/files/session.d/org.redbear.session.conf` + +The §16 G1 honesty flag documented a wire-mismatch: the activation file, +session policy, and recipe comment said `org.kde.StatusNotifierWatcher` but +the daemon `const BUS_NAME` and `#[interface(name)]` said +`org.freedesktop.StatusNotifierWatcher`. At runtime, D-Bus activation fired +for `org.kde` while the daemon registered as `org.freedesktop`, making the +service invisible to Qt tray clients. + +Round 3 closes this gap end-to-end: +- **`const BUS_NAME` updated** to `org.kde.StatusNotifierWatcher`. +- **`#[interface(name = ...)]` updated** to match. +- The item registry now canonicalizes paths as `` when + the argument starts with `/`, or passes bus names through unchanged. This + resolves the multi-client collision: two legitimate clients registering the + same conventional `/StatusNotifierItem` path under different unique bus + names now coexist correctly. +- A new `purge_for_name_owner_change(name, old_owner, new_owner)` method + filters purges to vanished unique bus names only — well-known name + releases no longer trigger purges. +- A `SignalEmitter` is built once at listener setup, and purge now emits + `StatusNotifierItemUnregistered` / `StatusNotifierHostUnregistered` for + every removed entry, so clients see consistent notifications. + +29/29 host tests pass (was 26; +3 new: `well_known_name_release_does_not_trigger_purge`, +`two_clients_register_same_path_under_different_senders_both_registered`, +`purge_emits_unregister_signals`). + +### R2. sessiond — inhibitor lifecycle (sender + FD close) + +**Path:** `local/recipes/system/redbear-sessiond/source/src/manager.rs`, +`local/recipes/system/redbear-sessiond/source/src/runtime_state.rs` + +§16 G8 incorrectly stated inhibitor reaping was missing. The reaping-by-sender +(commit `4522bc39ca`) was already in place; what was actually missing was +reaping-on-FD-close — required by the logind contract when the returned +inhibitor FD is closed. + +Round 3 fixes: +- `InhibitorEntry.inhibitor_fd: Option` now tracks the caller-side + FD the daemon hands back. +- `Inhibit()` spawns a tokio task that polls the caller-side FD for `POLLHUP` + via `nix::poll::poll`. When the caller closes their end, the task calls + `remove_inhibitor_for_fd(fd)` which scans for and removes the matching + inhibitor entry, closing the daemon-side FD copy. +- The existing `NameOwnerChanged` reaper remains the fallback for lost-bus- + owner cleanup. + +63/63 host tests pass (was 60; +3 new: `closing_caller_fd_removes_inhibitor`, +`multiple_inhibitors_same_sender_independent_fd_close`, +`inhibitor_fd_closure_does_not_affect_other_sender`). + +### R3. redbear-notifications — full lifecycle + +**Path:** `local/recipes/system/redbear-notifications/source/src/main.rs` + +The §16 G2 documented sender validation and InvokeAction checking but missed +the full lifecycle. Round 3 adds: +- **bounded at `MAX_NOTIFICATIONS=1024`** with FIFO eviction of the oldest + entry; sender-to-id index is pruned on eviction. +- **`replaces_id` semantics**: non-zero replaces_id updates the existing + record (same ID preserved) instead of allocating a new one. Per the + freedesktop spec, this is how updates work. +- **expiry-timeout sweep**: `NotificationRecord.expires_at: Option` + computed from `expire_timeout`. A background `spawn_expiry_sweeper` task + running every 500ms removes expired records and emits + `NotificationClosed` with `reason=EXPIRED (1)`. +- **sender-loss purge**: `sender_to_ids: HashMap>` reverse + index. `spawn_sender_reaper` runs every 2s polling + `DBusProxy::name_has_owner` for each tracked sender; vanished unique + senders are purged and their records emitted as closed. +- **`InvokeAction` cleanup**: removes the record after emitting + `ActionInvoked`, per the freedesktop spec (action completes the lifecycle). +- State model refactored: `Notifications` now wraps an + `Arc` so background sweepers share state with the + D-Bus interface without cloning the full struct. + +16/16 host tests pass (was 8; +8 new covering replaces_id, expiry, +vanished sender purge, bounded eviction, invoke_action cleanup). + +### R4. redbear-wifictl — NM AP interface served at returned paths + +**Path:** `local/recipes/system/redbear-wifictl/source/src/dbus_nm.rs` + +§16 G10 noted the NM interface was a static facade. Round 3 adds real AP +objects at the returned paths: +- New `AccessPointInterface` (`#[interface(name = + "org.freedesktop.NetworkManager.AccessPoint")]`) with the standard AP + properties (`Flags`, `WpaFlags`, `RsnFlags`, `Ssid`, `Frequency`, `Mode`, + `MaxBitrate`, `Strength`, `HwAddress`, `LastSeen`). +- `serve_on_thread` builds a `(path, AccessPointInterface)` pair for each AP + in the snapshot and calls `serve_at()` for each, so every path returned + by `GetAccessPoints()` now refers to a real D-Bus object. +- `active_access_point_inner()` finds the actual index of the access point + whose `ssid == active_ssid` and returns that path. No-match returns the + `/` sentinel. + +49/49 host tests pass (was 43; +6 new covering AP interface properties, +active-index lookup, and owned-object-path serving). + +### R5. Mesa — batch pool follow-on defects + +**Paths:** `local/recipes/libs/mesa/source/src/gallium/winsys/redox/drm/` + +§16 G13 noted the batch pool class math fix (commit `16f74ab87c`) left three +follow-on defects. Round 3 closes all three: + +- **Zero-byte BO allocation** (`redox_drm_bo.c`): `redox_resource_byte_count` + now handles `PIPE_BUFFER` and `format == NONE` specially by returning + `width0 * height0 * depth0`. The generic format path returned 0 for NONE + format, producing a zero-byte GEM object — the root cause of the batch + pool zero-size allocation. +- **`mtx_init` unchecked** (`redox_drm_winsys.c`, `.h`): the (void) cast + on `mtx_init` is removed; initialization failure now returns false from + `redox_drm_initialize()`. A new `batch_pool_mutex_initialized: bool` flag + on the winsys struct ensures `redox_drm_destroy()` only destroys an + initialized mutex, and the mutex is cleaned up on the `cs_create` + failure path. +- **Oversized batch size** (`redox_drm_cs.c`): `batch_bo_acquire` now + rejects `byte_count > UINT32_MAX` upfront. `width0` is `uint32_t`; values + above `UINT32_MAX` would truncate and overflow the subsequent memcpy. + +Verified by `gcc -c` compile-check on the host (the C code cross-compiles +for the Redox target; no host build). + +### R6. Documentation corrections + +§16 itself contained three factual errors flagged by the round-3 review +context-mining lane: + +1. **§16 G1 honesty flag** — claimed BUS_NAME was unchanged at + `org.freedesktop`. **Corrected**: §17 R1 above documents the round-3 + fix that changed BUS_NAME and the interface annotation to + `org.kde.StatusNotifierWatcher`. + +2. **§16 §3.3 note** — claimed `redbear-statusnotifierwatcher` is + "absent from every `redbear-*` config". **Corrected**: §17 row in §3.3 + above acknowledges the daemon IS wired into `redbear-full.toml:233`. + +3. **§16 G8/G9/G14** — claimed inhibitor reaping was "NOT APPLIED". + **Corrected**: §17 R2 confirms reaping was already applied + (commit `4522bc39ca`) and round 3 closed the remaining FD-close gap. + +### R7. Remaining gaps (not closed by round 3) + +- **dbus_nm backend wiring (R10 in §16):** the daemon still uses a static + `NmWifiDevice::new_disconnected("wlan0")` snapshot. Connecting it to the + live `redbear-wifictl` backend (`redbear-netctl`, scheme:network, etc.) + requires wiring through `main.rs`, which is out of scope for this round + (NM deferral per the DBUS-INTEGRATION-PLAN.md §3.4). +- **NM root StateChanged signal (R12):** no `#[zbus(signal)]` declaration + for StateChanged on the root or device interface. Clients must poll + `State` rather than subscribe to changes. This is a larger interface + design change deferred to the NM backend wiring round. +- **write_all error propagation (R7/G7):** still partial. sessiond has it; + `redbear-authd`, `redbear-udisks`, and `redbear-cpufreqd` do not. + Fixing these is straightforward but unrelated to the D-Bus integration + round scope. +- **Hardcoded root password (R11/G11):** `config/redbear-full.toml:102-106` + still has `password = "password"` in plaintext. Operator decision — + out of scope per the user's explicit instruction "do not touch hardcoded + root password". +- **Canonical image build (R14):** no `./local/scripts/build-redbear.sh + redbear-full` run was performed for any round. All runtime claims remain + build-wired, not runtime-proven. A canonical QEMU-based runtime gate is + the recommended next step before declaring these daemons ready for the + desktop path.