Register once

services.AddBeepAppUpdates();                 // reads update-settings.json beside the app
// or explicitly:
services.AddBeepAppUpdates(new UpdateSettings {
    FeedUrl        = "https://updates.example.com/stable/feed.json",
    Channel        = "stable",
    Mode           = UpdateMode.Optional,     // Required blocks the app until applied
    CurrentVersion = "1.2.0",
    InstallRoot    = @"C:\Program Files\MyApp"
});
var updates = provider.GetRequiredService<IAppUpdateService>();
var check = await updates.CheckAsync();
if (check.AppUpdateAvailable)  await updates.ApplyAppUpdateAsync(check, progress);
if (check.StaleModules.Count > 0) await updates.ApplyModuleUpdatesAsync(check);
await updates.RollbackAsync();                 // flip current back to the previous version

The pieces

ComponentRole
UpdateFeedClientAsync fetch/parse of feed.json and the payload manifest; every artifact SHA-256-verified before use (a mismatch is discarded). Malformed input → a named UpdateFeedException, never a silent null. Transport is injectable (HTTPS with a transparent local/UNC-folder fallback).
DeltaPlannerPure diff of the new manifest against the installed one over blob hashes, not files — so a rename or duplicate downloads nothing. Emits blobs-to-fetch, files-to-write/delete, and download-vs-full bytes.
SideBySideApplierStages into app-<ver>, verifies each blob, flips the current junction (IDirectoryLink). Running version never written; corrupt blob aborts pre-flip; rollback + retire.
ModuleUpdaterGoverns the existing NuGet UpdateService: the feed's pinned version wins over "latest", a required stale module blocks start, each fetched package is hash-verified. Applies on next launch.
AppUpdateServiceThe IAppUpdateService façade composing all of the above, plus best-effort telemetry (check/apply-*/modules-*) to a configured endpoint.

What the check tells you

UpdateCheckResult is a data snapshot: AppUpdateAvailable, RequiresFullInstall (installed version is below minSupportedVersion), StaleModules, and AnyUpdateAvailable. Subscribe to IAppUpdateService.UpdateAvailable to drive an Optional-mode notification.

Two ways to drive the UI

Use the WinForms/WPF extensions for a drop-in dialog, or the /CHECKUPDATE and /UPDATE CLI verbs, or call IAppUpdateService directly from your own UI. All three share the same engine.