[system-health-check] check_and_set_system_health

Merges check_system_health and set_active_configuration_healthy into a
single new operation with the following semantics:

- If the current configuration is marked "Pending" according to the
  paver, run health checks. If they fail, return an error. Eventually,
  seeing this error will cause system-update-checker and omaha-client
  to trigger reboot.

- Otherwise, mark the current configuration as healthy and the
  alternate configuration as unbootable.

Test: did an OTA, checked that the system logs were as expected on
  first boot (set healthy followed by set unbootable), and that
  //src/sys/pkg:tests passed on the newly updated system. Rebooted the
  machine and repeated this process, verifying the "skipping health
  checks" message appeared.

Fixed: 51480
Change-Id: I90a0952b3fe702f1f90184bd4497b00c98438f80
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/442202
Reviewed-by: Aaron Wood <aaronwood@google.com>
Reviewed-by: Zach Kirschenbaum <zkbaum@google.com>
Reviewed-by: John Wittrock <wittrock@google.com>
Testability-Review: Zach Kirschenbaum <zkbaum@google.com>
Commit-Queue: Hunter Freyer <hjfreyer@google.com>
diff --git a/src/sys/pkg/bin/omaha-client/src/main.rs b/src/sys/pkg/bin/omaha-client/src/main.rs
index e119f6e..8dc5e2b 100644
--- a/src/sys/pkg/bin/omaha-client/src/main.rs
+++ b/src/sys/pkg/bin/omaha-client/src/main.rs
@@ -176,14 +176,15 @@
 }
 
 async fn check_and_set_system_health_impl() -> Result<(), Error> {
-    system_health_check::check_system_health().await?;
-
     let paver = fuchsia_component::client::connect_to_service::<PaverMarker>()?;
     let (boot_manager, boot_manager_server_end) = ::fidl::endpoints::create_proxy()?;
 
     paver
         .find_boot_manager(boot_manager_server_end)
         .context("transport error while calling find_boot_manager()")?;
-
-    system_health_check::set_active_configuration_healthy(&boot_manager).await
+    // NOTE(fxbug.dev/63642): The docs for check_and_set_system_health say that we should respond to
+    // an error here by rebooting, but we'll be refactoring this away Soon™, so for now we just log
+    // it.
+    system_health_check::check_and_set_system_health(&boot_manager).await?;
+    Ok(())
 }
diff --git a/src/sys/pkg/bin/system-update-checker/src/main.rs b/src/sys/pkg/bin/system-update-checker/src/main.rs
index 3f8d5cd..b6859a5 100644
--- a/src/sys/pkg/bin/system-update-checker/src/main.rs
+++ b/src/sys/pkg/bin/system-update-checker/src/main.rs
@@ -156,14 +156,15 @@
 }
 
 async fn check_and_set_system_health_impl() -> Result<(), Error> {
-    system_health_check::check_system_health().await?;
-
     let paver = fuchsia_component::client::connect_to_service::<PaverMarker>()?;
     let (boot_manager, boot_manager_server_end) = fidl::endpoints::create_proxy()?;
 
     paver
         .find_boot_manager(boot_manager_server_end)
         .context("transport error while calling find_boot_manager()")?;
-
-    system_health_check::set_active_configuration_healthy(&boot_manager).await
+    // NOTE(fxbug.dev/63642): The docs for check_and_set_system_health say that we should respond to
+    // an error here by rebooting, but we'll be refactoring this away Soon™, so for now we just log
+    // it.
+    system_health_check::check_and_set_system_health(&boot_manager).await?;
+    Ok(())
 }
diff --git a/src/sys/pkg/lib/system-health-check/BUILD.gn b/src/sys/pkg/lib/system-health-check/BUILD.gn
index 3f86efd..375aa94 100644
--- a/src/sys/pkg/lib/system-health-check/BUILD.gn
+++ b/src/sys/pkg/lib/system-health-check/BUILD.gn
@@ -23,13 +23,12 @@
     "//third_party/rust_crates:thiserror",
   ]
 
-  test_deps = [ "//src/sys/pkg/testing/mock-paver" ]
-
-  sources = [
-    "src/check.rs",
-    "src/lib.rs",
-    "src/mark.rs",
+  test_deps = [
+    "//src/sys/pkg/testing/mock-paver",
+    "//third_party/rust_crates:matches",
   ]
+
+  sources = [ "src/lib.rs" ]
 }
 
 fuchsia_unittest_package("system-health-check-tests") {
diff --git a/src/sys/pkg/lib/system-health-check/README.md b/src/sys/pkg/lib/system-health-check/README.md
index 6cc42cf..8fc27f8 100644
--- a/src/sys/pkg/lib/system-health-check/README.md
+++ b/src/sys/pkg/lib/system-health-check/README.md
@@ -1,11 +1,9 @@
 # System Health Check
 
-This crate exposes 2 helper functions, one to determine if a running system is
-healthy after an OTA, and another to inform the paver service that the current 
-system is healthy.
+This crate exposes a helper function that determines if a running system is
+healthy after an OTA, and if it is, informs the paver service.
 
 Since a system can be configured to either use the system-update-checker or
 omaha-client components to discover and initiate base package updates and both
-components need the ability to check the system health, this functionality is
-maintained in this crate shared by both components.
-
+components need the ability to check and update the system health, this
+functionality is maintained in this crate shared by both components.
\ No newline at end of file
diff --git a/src/sys/pkg/lib/system-health-check/src/check.rs b/src/sys/pkg/lib/system-health-check/src/check.rs
deleted file mode 100644
index 9cae81b..0000000
--- a/src/sys/pkg/lib/system-health-check/src/check.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use anyhow::Error;
-
-/// Future resolves once it has determined the system's health state.
-/// Returns Ok(()) on healthy and Err(reason) on unhealthy.
-/// Used after a system update to determine if the device should be rolled back.
-pub async fn check_system_health() -> Result<(), Error> {
-    Ok(())
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use fuchsia_async::{self as fasync};
-
-    #[fasync::run_singlethreaded(test)]
-    async fn test_succeeds() -> Result<(), Error> {
-        check_system_health().await
-    }
-}
diff --git a/src/sys/pkg/lib/system-health-check/src/lib.rs b/src/sys/pkg/lib/system-health-check/src/lib.rs
index a5fa1f8..4966fc4 100644
--- a/src/sys/pkg/lib/system-health-check/src/lib.rs
+++ b/src/sys/pkg/lib/system-health-check/src/lib.rs
@@ -2,8 +2,336 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-mod check;
-mod mark;
+use {
+    fidl_fuchsia_paver as paver, fuchsia_syslog::fx_log_info, fuchsia_zircon::Status,
+    thiserror::Error,
+};
 
-pub use check::check_system_health;
-pub use mark::set_active_configuration_healthy;
+/// Error condition that may be returned by check_and_set_system_health.
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("health check failed")]
+    HealthCheck(#[source] anyhow::Error),
+
+    #[error("the current configuration ({_0:?}) is unbootable. This should never happen.")]
+    CurrentConfigurationUnbootable(paver::Configuration),
+
+    #[error("BootManager returned non-ok status while calling {method_name:}")]
+    BootManagerStatus {
+        method_name: &'static str,
+        #[source]
+        status: Status,
+    },
+
+    #[error("fidl error while calling BootManager method {method_name:}")]
+    BootManagerFidl {
+        method_name: &'static str,
+        #[source]
+        error: fidl::Error,
+    },
+}
+
+/// Helper to convert fidl's nested errors.
+trait BootManagerResultExt {
+    type T;
+
+    fn into_boot_manager_result(self, method_name: &'static str) -> Result<Self::T, Error>;
+}
+
+impl BootManagerResultExt for Result<i32, fidl::Error> {
+    type T = ();
+
+    fn into_boot_manager_result(
+        self: Result<i32, fidl::Error>,
+        method_name: &'static str,
+    ) -> Result<(), Error> {
+        match self.map(Status::ok) {
+            Ok(Ok(())) => Ok(()),
+            Ok(Err(status)) => Err(Error::BootManagerStatus { status, method_name }),
+            Err(error) => Err(Error::BootManagerFidl { error, method_name }),
+        }
+    }
+}
+
+impl<T> BootManagerResultExt for Result<Result<T, i32>, fidl::Error> {
+    type T = T;
+
+    fn into_boot_manager_result(
+        self: Result<Result<Self::T, i32>, fidl::Error>,
+        method_name: &'static str,
+    ) -> Result<Self::T, Error> {
+        match self {
+            Ok(Ok(value)) => Ok(value),
+            Ok(Err(raw)) => {
+                Err(Error::BootManagerStatus { status: Status::from_raw(raw), method_name })
+            }
+            Err(error) => Err(Error::BootManagerFidl { error, method_name }),
+        }
+    }
+}
+
+/// Puts BootManager metadata into a happy state, provided we believe the system can OTA.
+///
+/// The "happy state" is:
+/// - The current configuration is active and marked Healthy.
+/// - The alternate configuration is marked Unbootable.
+///
+/// First we decide whether the system is likely to be functional enough to apply an OTA:
+/// - If the current configuration is marked Pending, we run a variety of checks and return
+///   Error::HealthCheck if they fail.
+/// - If the current configuration is marked Healthy, it means the system already passed the health
+///   checks at some point. We assume they would still pass, and skip them.
+/// - If the current configuration is marked Unbootable, and this function returns
+///   Error::CurrentConfigurationUnbootable, because that should never happen.
+///
+/// Assuming we get through all that, we tell the paver to mark the current configuration Healthy
+/// and the alternate configuration Unbootable.
+///
+/// As a special case, if the current configuration is Recovery, we return Ok without performing any
+/// checks or making any changes.
+///
+/// If this returns an error, it likely means that the system is somehow busted, and that it should
+/// be rebooted. Rebooting will hopefully either fix the issue or decrement the boot counter,
+/// eventually leading to a rollback.
+pub async fn check_and_set_system_health(
+    boot_manager: &paver::BootManagerProxy,
+) -> Result<(), Error> {
+    let (current_config, alternate_config) = match boot_manager
+        .query_current_configuration()
+        .await
+        .into_boot_manager_result("query_current_configuration")
+    {
+        Err(Error::BootManagerFidl {
+            error: fidl::Error::ClientChannelClosed { status: Status::NOT_SUPPORTED, .. },
+            ..
+        }) => {
+            fx_log_info!("ABR not supported: skipping health checks and boot metadata updates");
+            return Ok(());
+        }
+        Err(e) => return Err(e),
+
+        Ok(paver::Configuration::Recovery) => {
+            fx_log_info!("System in recovery: skipping health checks and boot metadata updates");
+            return Ok(());
+        }
+
+        Ok(paver::Configuration::A) => (paver::Configuration::A, paver::Configuration::B),
+        Ok(paver::Configuration::B) => (paver::Configuration::B, paver::Configuration::A),
+    };
+
+    // Note: at this point, we know that ABR is supported and we're not in Recovery.
+    let current_config_status = boot_manager
+        .query_configuration_status(current_config)
+        .await
+        .into_boot_manager_result("query_configuration_status")?;
+
+    // Run the health checks if `current_config` isn't already marked `Healthy`, and pass any
+    // failures up to the caller.
+    match current_config_status {
+        paver::ConfigurationStatus::Healthy => {
+            fx_log_info!("current configuration is already healthy; skipping health checks");
+        }
+        paver::ConfigurationStatus::Unbootable => {
+            return Err(Error::CurrentConfigurationUnbootable(current_config))
+        }
+        paver::ConfigurationStatus::Pending => {
+            // Bail out if the system is unhealthy.
+            let () = check_system_health().await.map_err(Error::HealthCheck)?;
+        }
+    }
+
+    // Do all writes inside this function to ensure that we call flush no matter what.
+    async fn internal_write(
+        boot_manager: &paver::BootManagerProxy,
+        current_config: paver::Configuration,
+        alternate_config: paver::Configuration,
+    ) -> Result<(), Error> {
+        let () = boot_manager
+            .set_configuration_healthy(current_config)
+            .await
+            .into_boot_manager_result("set_configuration_healthy")?;
+        let () = boot_manager
+            .set_configuration_unbootable(alternate_config)
+            .await
+            .into_boot_manager_result("set_configuration_unbootable")?;
+        Ok(())
+    }
+
+    // Capture the result of the writes so we can return it after we flush.
+    let write_result = internal_write(boot_manager, current_config, alternate_config).await;
+
+    let () = boot_manager.flush().await.into_boot_manager_result("flush")?;
+
+    write_result
+}
+
+/// Dummy function to indicate where health checks will eventually go, and how to handle associated
+/// errors.
+async fn check_system_health() -> Result<(), anyhow::Error> {
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    #![cfg(test)]
+    use {
+        super::*,
+        fidl_fuchsia_paver::Configuration,
+        fuchsia_async as fasync,
+        fuchsia_zircon::Status,
+        matches::assert_matches,
+        mock_paver::{MockPaverServiceBuilder, PaverEvent},
+        std::sync::Arc,
+    };
+
+    async fn run_with_healthy_current(
+        current_config: Configuration,
+        alternate_config: Configuration,
+    ) {
+        let paver = Arc::new(
+            MockPaverServiceBuilder::new()
+                .current_config(current_config)
+                .config_status_hook(|_| paver::ConfigurationStatus::Healthy)
+                .build(),
+        );
+        check_and_set_system_health(&paver.spawn_boot_manager_service()).await.unwrap();
+        assert_eq!(
+            paver.take_events(),
+            vec![
+                PaverEvent::QueryCurrentConfiguration,
+                PaverEvent::QueryConfigurationStatus { configuration: current_config },
+                PaverEvent::SetConfigurationHealthy { configuration: current_config },
+                PaverEvent::SetConfigurationUnbootable { configuration: alternate_config },
+                PaverEvent::BootManagerFlush,
+            ]
+        );
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_check_and_set_healthy_config_a() {
+        run_with_healthy_current(Configuration::A, Configuration::B).await
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_check_and_set_healthy_config_b() {
+        run_with_healthy_current(Configuration::B, Configuration::A).await
+    }
+
+    async fn run_with_pending_current(
+        current_config: Configuration,
+        alternate_config: Configuration,
+    ) {
+        let paver = Arc::new(
+            MockPaverServiceBuilder::new()
+                .current_config(current_config)
+                .config_status_hook(|_| paver::ConfigurationStatus::Pending)
+                .build(),
+        );
+        check_and_set_system_health(&paver.spawn_boot_manager_service()).await.unwrap();
+        assert_eq!(
+            paver.take_events(),
+            vec![
+                PaverEvent::QueryCurrentConfiguration,
+                PaverEvent::QueryConfigurationStatus { configuration: current_config },
+                // The health check gets performed here, but we don't see any side-effects.
+                PaverEvent::SetConfigurationHealthy { configuration: current_config },
+                PaverEvent::SetConfigurationUnbootable { configuration: alternate_config },
+                PaverEvent::BootManagerFlush,
+            ]
+        );
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_check_and_set_pending_config_a() {
+        run_with_pending_current(Configuration::A, Configuration::B).await
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_check_and_set_pending_config_b() {
+        run_with_pending_current(Configuration::B, Configuration::A).await
+    }
+
+    async fn run_with_unbootable_current(current_config: Configuration) {
+        let paver = Arc::new(
+            MockPaverServiceBuilder::new()
+                .current_config(current_config)
+                .config_status_hook(|_| paver::ConfigurationStatus::Unbootable)
+                .build(),
+        );
+        assert_matches!(
+            check_and_set_system_health(&paver.spawn_boot_manager_service()).await,
+            Err(Error::CurrentConfigurationUnbootable(cc))
+            if cc == current_config
+        );
+        assert_eq!(
+            paver.take_events(),
+            vec![
+                PaverEvent::QueryCurrentConfiguration,
+                PaverEvent::QueryConfigurationStatus { configuration: current_config },
+            ]
+        );
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_check_and_set_unbootable_config_a_returns_error() {
+        run_with_unbootable_current(Configuration::A).await
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_check_and_set_unbootable_config_b_returns_error() {
+        run_with_unbootable_current(Configuration::B).await
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_does_not_change_metadata_when_device_does_not_support_abr() {
+        let paver = Arc::new(
+            MockPaverServiceBuilder::new()
+                .boot_manager_close_with_epitaph(Status::NOT_SUPPORTED)
+                .build(),
+        );
+
+        check_and_set_system_health(&paver.spawn_boot_manager_service()).await.unwrap();
+        assert_eq!(paver.take_events(), vec![]);
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_does_not_change_metadata_when_device_in_recovery() {
+        let paver = Arc::new(
+            MockPaverServiceBuilder::new()
+                .current_config(Configuration::Recovery)
+                .config_status_hook(|_| paver::ConfigurationStatus::Healthy)
+                .build(),
+        );
+        check_and_set_system_health(&paver.spawn_boot_manager_service()).await.unwrap();
+        assert_eq!(paver.take_events(), vec![PaverEvent::QueryCurrentConfiguration]);
+    }
+
+    #[fasync::run_singlethreaded(test)]
+    async fn test_fails_when_set_healthy_fails() {
+        let paver = Arc::new(
+            MockPaverServiceBuilder::new()
+                .call_hook(|e| match e {
+                    PaverEvent::SetConfigurationHealthy { .. } => Status::OUT_OF_RANGE,
+                    _ => Status::OK,
+                })
+                .build(),
+        );
+
+        assert_matches!(
+            check_and_set_system_health(&paver.spawn_boot_manager_service()).await,
+            Err(Error::BootManagerStatus {
+                method_name: "set_configuration_healthy",
+                status: Status::OUT_OF_RANGE
+            })
+        );
+        assert_eq!(
+            paver.take_events(),
+            vec![
+                PaverEvent::QueryCurrentConfiguration,
+                PaverEvent::QueryConfigurationStatus { configuration: Configuration::A },
+                PaverEvent::SetConfigurationHealthy { configuration: Configuration::A },
+                PaverEvent::BootManagerFlush,
+            ]
+        );
+    }
+}
diff --git a/src/sys/pkg/lib/system-health-check/src/mark.rs b/src/sys/pkg/lib/system-health-check/src/mark.rs
deleted file mode 100644
index 3714a7e..0000000
--- a/src/sys/pkg/lib/system-health-check/src/mark.rs
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use {
-    anyhow::{format_err, Context},
-    fidl_fuchsia_paver::{BootManagerProxy, Configuration},
-    fuchsia_syslog::fx_log_info,
-    fuchsia_zircon::Status,
-};
-
-/// Inform the Paver service that Fuchsia booted successfully, so it marks the partition healthy
-/// and stops decrementing the boot counter.
-pub async fn set_active_configuration_healthy(
-    boot_manager: &BootManagerProxy,
-) -> Result<(), anyhow::Error> {
-    // TODO(51480): This should use the "current" configuration, not active, when they differ.
-    let active_config = match boot_manager
-        .query_active_configuration()
-        .await
-        .map(|res| res.map_err(Status::from_raw))
-    {
-        Ok(Ok(active_config)) => active_config,
-        Err(fidl::Error::ClientChannelClosed { status: Status::NOT_SUPPORTED, .. }) => {
-            fx_log_info!("ABR not supported");
-            return Ok(());
-        }
-        Ok(Err(Status::NOT_SUPPORTED)) => {
-            fx_log_info!("no partition is active; we're in recovery");
-            return Ok(());
-        }
-        Err(e) => {
-            return Err(e).context("transport error while calling query_active_configuration()")
-        }
-        Ok(Err(e)) => {
-            return Err(e).context("paver error while calling query_active_configuration()")
-        }
-    };
-
-    // Note: at this point, we know that ABR is supported.
-    boot_manager
-        .set_configuration_healthy(active_config)
-        .await
-        .map(Status::ok)
-        .with_context(|| {
-            format!("transport error while calling set_configuration_healthy({:?})", active_config)
-        })?
-        .with_context(|| {
-            format!("paver error while calling set_configuration_healthy({:?})", active_config)
-        })?;
-
-    // Find out the inactive partition and mark it as unbootable.
-    let inactive_config = match active_config {
-        Configuration::A => Configuration::B,
-        Configuration::B => Configuration::A,
-        Configuration::Recovery => return Err(format_err!("Recovery should not be active")),
-    };
-    boot_manager
-        .set_configuration_unbootable(inactive_config)
-        .await
-        .map(Status::ok)
-        .with_context(|| {
-            format!(
-                "transport error while calling set_configuration_unbootable({:?})",
-                inactive_config
-            )
-        })?
-        .with_context(|| {
-            format!("paver error while calling set_configuration_unbootable({:?})", inactive_config)
-        })?;
-
-    boot_manager
-        .flush()
-        .await
-        .map(Status::ok)
-        .context("transport error while calling flush()")?
-        .context("paver error while calling flush()")?;
-
-    Ok(())
-}
-
-#[cfg(test)]
-mod tests {
-    use {
-        super::*,
-        fidl_fuchsia_paver::Configuration,
-        fuchsia_async as fasync,
-        fuchsia_zircon::Status,
-        mock_paver::{MockPaverServiceBuilder, PaverEvent},
-        std::sync::Arc,
-    };
-
-    // We should call SetConfigurationUnbootable when the device supports ABR and is not in recovery
-    #[fasync::run_singlethreaded(test)]
-    async fn test_calls_set_configuration_unbootable_config_a_active() {
-        let paver_service =
-            Arc::new(MockPaverServiceBuilder::new().active_config(Configuration::A).build());
-        set_active_configuration_healthy(&paver_service.spawn_boot_manager_service())
-            .await
-            .expect("setting active succeeds");
-        assert_eq!(
-            paver_service.take_events(),
-            vec![
-                PaverEvent::QueryActiveConfiguration,
-                PaverEvent::SetConfigurationHealthy { configuration: Configuration::A },
-                PaverEvent::SetConfigurationUnbootable { configuration: Configuration::B },
-                PaverEvent::BootManagerFlush
-            ]
-        );
-    }
-
-    // We should call SetConfigurationUnbootable when the device supports ABR and is not in recovery
-    #[fasync::run_singlethreaded(test)]
-    async fn test_calls_set_configuration_unbootable_config_b_active() {
-        let paver_service =
-            Arc::new(MockPaverServiceBuilder::new().active_config(Configuration::B).build());
-        set_active_configuration_healthy(&paver_service.spawn_boot_manager_service())
-            .await
-            .expect("setting active succeeds");
-        assert_eq!(
-            paver_service.take_events(),
-            vec![
-                PaverEvent::QueryActiveConfiguration,
-                PaverEvent::SetConfigurationHealthy { configuration: Configuration::B },
-                PaverEvent::SetConfigurationUnbootable { configuration: Configuration::A },
-                PaverEvent::BootManagerFlush
-            ]
-        );
-    }
-
-    #[fasync::run_singlethreaded(test)]
-    async fn test_does_not_change_metadata_when_device_does_not_support_abr() {
-        let paver_service = Arc::new(
-            MockPaverServiceBuilder::new()
-                .boot_manager_close_with_epitaph(Status::NOT_SUPPORTED)
-                .build(),
-        );
-        set_active_configuration_healthy(&paver_service.spawn_boot_manager_service())
-            .await
-            .expect("setting active succeeds");
-        assert_eq!(paver_service.take_events(), Vec::new());
-    }
-
-    #[fasync::run_singlethreaded(test)]
-    async fn test_does_not_change_metadata_when_device_in_recovery() {
-        let paver_service =
-            Arc::new(MockPaverServiceBuilder::new().active_config(Configuration::Recovery).build());
-        set_active_configuration_healthy(&paver_service.spawn_boot_manager_service())
-            .await
-            .expect("setting active succeeds");
-        assert_eq!(paver_service.take_events(), vec![PaverEvent::QueryActiveConfiguration]);
-    }
-}
diff --git a/src/sys/pkg/tests/omaha-client/src/lib.rs b/src/sys/pkg/tests/omaha-client/src/lib.rs
index 088a0e5..286ffd7 100644
--- a/src/sys/pkg/tests/omaha-client/src/lib.rs
+++ b/src/sys/pkg/tests/omaha-client/src/lib.rs
@@ -337,46 +337,51 @@
     }
 }
 
-// Test will hang if omaha-client does not call set_configuration_healthy on the paver service.
+// Test will hang if omaha-client does not contact the paver service.
 #[fasync::run_singlethreaded(test)]
-async fn test_calls_set_configuration_healthy() {
-    let (send, recv) = oneshot::channel();
-    let send = Mutex::new(Some(send));
+async fn test_calls_paver_service() {
+    let (send, recv) = mpsc::unbounded();
     let paver = MockPaverServiceBuilder::new()
-        .call_hook(move |event| {
-            match event {
-                PaverEvent::SetConfigurationHealthy { configuration } => {
-                    send.lock().take().unwrap().send(*configuration).unwrap();
-                }
-                _ => {}
-            }
-            Status::OK
-        })
+        .current_config(Configuration::A)
+        .event_hook(move |e| send.unbounded_send(e.to_owned()).expect("channel stayed open"))
         .build();
     let _env = TestEnvBuilder::new().paver(paver).build();
 
-    // wait for the call hook to notify `send`.
-    assert_matches!(recv.await, Ok(Configuration::A));
+    assert_eq!(
+        recv.take(5).collect::<Vec<PaverEvent>>().await,
+        vec![
+            PaverEvent::QueryCurrentConfiguration,
+            PaverEvent::QueryConfigurationStatus { configuration: Configuration::A },
+            PaverEvent::SetConfigurationHealthy { configuration: Configuration::A },
+            PaverEvent::SetConfigurationUnbootable { configuration: Configuration::B },
+            PaverEvent::BootManagerFlush
+        ]
+    );
 }
 
-// Test will hang if omaha-client does not call set_configuration_healthy on the paver service.
+// Test will hang if omaha-client does not contact the paver service.
 #[fasync::run_singlethreaded(test)]
-async fn test_update_manager_checknow_works_after_set_configuration_healthy_fails() {
-    let (send, recv) = oneshot::channel();
-    let send = Mutex::new(Some(send));
+async fn test_update_manager_checknow_works_after_paver_service_fails() {
+    let (send, recv) = mpsc::unbounded();
     let paver = MockPaverServiceBuilder::new()
+        .current_config(Configuration::B)
+        .event_hook(move |e| send.unbounded_send(e.to_owned()).expect("channel stayed open"))
         .call_hook(move |event| match event {
-            PaverEvent::SetConfigurationHealthy { configuration } => {
-                send.lock().take().unwrap().send(*configuration).unwrap();
-                Status::INTERNAL
-            }
+            PaverEvent::SetConfigurationHealthy { .. } => Status::INTERNAL,
             _ => Status::OK,
         })
         .build();
     let env = TestEnvBuilder::new().paver(paver).build();
 
-    // wait for the call hook to notify `send`.
-    assert_matches!(recv.await, Ok(Configuration::A));
+    assert_eq!(
+        recv.take(4).collect::<Vec<PaverEvent>>().await,
+        vec![
+            PaverEvent::QueryCurrentConfiguration,
+            PaverEvent::QueryConfigurationStatus { configuration: Configuration::B },
+            PaverEvent::SetConfigurationHealthy { configuration: Configuration::B },
+            PaverEvent::BootManagerFlush
+        ]
+    );
 
     let mut stream = env.check_now().await;
     assert_matches!(stream.next().await, Some(_));
diff --git a/src/sys/pkg/tests/system-update-checker/src/lib.rs b/src/sys/pkg/tests/system-update-checker/src/lib.rs
index 5f56527..3cdd74e 100644
--- a/src/sys/pkg/tests/system-update-checker/src/lib.rs
+++ b/src/sys/pkg/tests/system-update-checker/src/lib.rs
@@ -215,8 +215,8 @@
     assert_eq!(
         env.proxies.paver_events.take(2).collect::<Vec<PaverEvent>>().await,
         vec![
-            PaverEvent::QueryActiveConfiguration,
-            PaverEvent::SetConfigurationHealthy { configuration: Configuration::A }
+            PaverEvent::QueryCurrentConfiguration,
+            PaverEvent::QueryConfigurationStatus { configuration: Configuration::A }
         ]
     );
 }
@@ -228,7 +228,7 @@
 
     assert_eq!(
         env.proxies.paver_events.take(1).collect::<Vec<PaverEvent>>().await,
-        vec![PaverEvent::QueryActiveConfiguration]
+        vec![PaverEvent::QueryCurrentConfiguration]
     );
 
     assert_eq!(
@@ -244,7 +244,7 @@
 
     assert_eq!(
         env.proxies.paver_events.take(1).collect::<Vec<PaverEvent>>().await,
-        vec![PaverEvent::QueryActiveConfiguration]
+        vec![PaverEvent::QueryCurrentConfiguration]
     );
 
     let (client_end, request_stream) =