Prognosis 3.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Prognosis --version 3.0.0
                    
NuGet\Install-Package Prognosis -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Prognosis" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Prognosis" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Prognosis" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Prognosis --version 3.0.0
                    
#r "nuget: Prognosis, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Prognosis@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Prognosis&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Prognosis&version=3.0.0
                    
Install as a Cake Tool

Prognosis

Unfiltered AI slop. Use at your own risk

A dependency-aware service health modeling library for .NET. Models the health of multiple services as a directed graph where each service's effective status is computed from its own intrinsic health and the weighted health of its dependencies.

graph TD
    Store["🛒 Online Store"] -->|"Required"| Checkout["Checkout"]
    Store -->|"Important"| Search["Product Search"]
    Store -->|"Optional"| Reviews["Reviews"]
    Checkout -->|"Required"| Payment["Payment Gateway"]
    Checkout -->|"Required"| Inventory["Inventory"]
    Payment -->|"Important"| Fraud["Fraud Detection"]
    Search -->|"Required"| Index["Search Index"]

    style Store fill:#22c55e,color:#fff
    style Checkout fill:#22c55e,color:#fff
    style Payment fill:#22c55e,color:#fff
    style Inventory fill:#22c55e,color:#fff
    style Fraud fill:#22c55e,color:#fff
    style Search fill:#22c55e,color:#fff
    style Index fill:#22c55e,color:#fff
    style Reviews fill:#22c55e,color:#fff

How it works: each service reports its own health and declares dependencies with an importance level. Prognosis walks the graph and computes the effective status — a Required dependency failing makes the parent unhealthy, an Important one degrades it, and an Optional one is ignored. If Fraud Detection goes down, Payment Gateway becomes degraded, which degrades Checkout, which degrades the whole store. If Payment Gateway itself goes down, Checkout becomes unhealthy — and since it's Required, the store is unhealthy too. If Reviews go down? Nothing happens.

Packages

Package Purpose
Prognosis Core library — health graph modeling, aggregation, monitoring, serialization
Prognosis.Reactive System.Reactive extensions — Rx-based polling, push-triggered reports, diff-based change streams
Prognosis.DependencyInjection Microsoft.Extensions.DependencyInjection integration — assembly scanning, fluent graph builder, hosted monitoring

Key concepts

Health statuses

Status Value Meaning
Healthy 0 Known good
Unknown 1 Not yet probed (startup state)
Degraded 2 Known partial failure
Unhealthy 3 Known failure

Ordered worst-is-highest so comparisons naturally surface the most severe status.

Dependency importance

Importance Default propagation rule
Required Dependency status passes through unchanged — an unhealthy dependency makes the parent unhealthy
Important Unhealthy is capped at Degraded for the parent; Unknown and Degraded pass through
Optional Dependency health is ignored entirely

These rules describe the default strategy (HealthAggregator.Aggregate). Propagation behavior can be changed per-service by supplying a custom aggregation strategy.

Usage patterns

1. Implement IHealthAware on a class you own

Expose a HealthNode property — no forwarding boilerplate:

class CacheService : IHealthAware
{
    public HealthNode Health { get; }

    public CacheService()
    {
        Health = new HealthCheck("Cache",
            () => IsConnected
                ? HealthStatus.Healthy
                : new HealthEvaluation(HealthStatus.Unhealthy, "Redis timeout"));
    }

    public bool IsConnected { get; set; } = true;
}

For services with fine-grained health attributes, use a HealthGroup backed by sub-nodes:

class DatabaseService : IHealthAware
{
    public HealthNode Health { get; }

    public bool IsConnected { get; set; } = true;
    public double AverageLatencyMs { get; set; } = 50;
    public double PoolUtilization { get; set; } = 0.3;

    public DatabaseService()
    {
        var connection = new HealthCheck("Database.Connection",
            () => IsConnected
                ? HealthStatus.Healthy
                : new HealthEvaluation(HealthStatus.Unhealthy, "Connection lost"));

        var latency = new HealthCheck("Database.Latency",
            () => AverageLatencyMs switch
            {
                > 500 => new HealthEvaluation(HealthStatus.Degraded,
                    $"Avg latency {AverageLatencyMs:F0}ms exceeds 500ms threshold"),
                _ => HealthStatus.Healthy,
            });

        var connectionPool = new HealthCheck("Database.ConnectionPool",
            () => PoolUtilization switch
            {
                >= 1.0 => new HealthEvaluation(HealthStatus.Unhealthy,
                    "Connection pool exhausted"),
                >= 0.9 => new HealthEvaluation(HealthStatus.Degraded,
                    $"Connection pool at {PoolUtilization:P0} utilization"),
                _ => HealthStatus.Healthy,
            });

        Health = new HealthGroup("Database")
            .DependsOn(connection, Importance.Required)
            .DependsOn(latency, Importance.Important)
            .DependsOn(connectionPool, Importance.Required);
    }
}

The sub-nodes show up automatically in EvaluateAll, CreateReport, and the JSON output. Reason strings chain from the leaf all the way up:

Database.Latency: Degraded — Avg latency 600ms exceeds 500ms threshold
Database: Degraded (3 dependencies) — Database.Latency: Avg latency 600ms exceeds 500ms threshold
AuthService: Degraded (2 dependencies) — Database: Database.Latency: ...

2. Wrap a service you can't modify

Use HealthCheck with a health-check delegate:

var emailHealth = new HealthCheck("EmailProvider",
    () => client.IsConnected
        ? HealthStatus.Healthy
        : new HealthEvaluation(HealthStatus.Unhealthy, "SMTP connection refused"));

3. Compose the graph

Wire services together with DependsOn or HealthDependency lists:

var authService = new HealthCheck("AuthService")
    .DependsOn(database.Health, Importance.Required)
    .DependsOn(cache.Health, Importance.Important);

// List style
var app = new HealthGroup("Application",
[
    new HealthDependency(authService, Importance.Required),
    new HealthDependency(notifications, Importance.Important),
]);

// Fluent style
var app = new HealthGroup("Application")
    .DependsOn(authService, Importance.Required)
    .DependsOn(notifications, Importance.Important);

Aggregation strategies

By default, health propagation follows the rules in Dependency importance via HealthAggregator.Aggregate. You can swap the strategy per-service to change how dependency statuses combine.

Built-in strategies

Strategy Behavior
HealthAggregator.Aggregate Default. Worst-case propagation — a single unhealthy Required dependency makes the parent unhealthy.
HealthAggregator.AggregateWithRedundancy Redundancy-aware — when at least one non-optional dependency is healthy, a Required dependency's unhealthy status is capped at Degraded. All non-optional deps must be unhealthy before the parent becomes unhealthy.

Using a built-in strategy

// Without redundancy (default) — if either service goes down, the parent is unhealthy.
var app = new HealthGroup("Application")
    .DependsOn(primaryDb, Importance.Required)
    .DependsOn(replicaDb, Importance.Required);

// With redundancy — if one goes down but the other is healthy, the parent is degraded.
var app = new HealthGroup("Application",
    aggregator: HealthAggregator.AggregateWithRedundancy)
    .DependsOn(primaryDb, Importance.Required)
    .DependsOn(replicaDb, Importance.Required);

Custom strategies

The AggregationStrategy delegate takes intrinsic health + dependencies and returns an evaluation. Pass any matching function:

var app = new HealthGroup("Application",
    aggregator: (intrinsic, deps) =>
    {
        // Your custom logic here
        return new HealthEvaluation(HealthStatus.Healthy);
    });

Strategies can also be applied to HealthCheck and HealthTracker:

// HealthCheck with a strategy
var svc = new HealthCheck("Service", healthCheck,
    aggregator: HealthAggregator.AggregateWithRedundancy);

// HealthTracker (when embedding in your own class)
var tracker = new HealthTracker(intrinsicCheck,
    aggregator: HealthAggregator.AggregateWithRedundancy);

Graph operations

// Evaluate a single service (walks its dependencies)
HealthEvaluation eval = app.Evaluate();

// Snapshot the entire graph (depth-first post-order)
IReadOnlyList<HealthSnapshot> snapshots = HealthAggregator.EvaluateAll(app);

// Package as a serialization-ready report with timestamp
HealthReport report = HealthAggregator.CreateReport(app);

// Detect circular dependencies
IReadOnlyList<IReadOnlyList<string>> cycles = HealthAggregator.DetectCycles(app);

// Diff two reports to find individual service changes
IReadOnlyList<StatusChange> changes = HealthAggregator.Diff(before, after);

Observable health monitoring

Every HealthNode node supports push-based notifications. Subscribe to individual services or monitor the full graph:

// Individual service notifications
database.Health.StatusChanged.Subscribe(observer);

// Graph-level polling with HealthMonitor
await using var monitor = new HealthMonitor([app], TimeSpan.FromSeconds(5));
monitor.ReportChanged.Subscribe(reportObserver);

// Manual poll (useful for testing or getting initial state)
monitor.Poll();

IObservable<T> is a BCL type — no System.Reactive dependency required. Add System.Reactive only when you want operators like DistinctUntilChanged() or Throttle().

Dependency injection

The Prognosis.DependencyInjection package provides a fluent builder for configuring the health graph within a hosted application:

builder.Services.AddPrognosis(health =>
{
    // Discover all IHealthAware implementations and wire [DependsOn<T>] attributes.
    health.ScanForServices(typeof(Program).Assembly);

    // Wrap a third-party service with a health delegate.
    // Name defaults to typeof(T).Name when omitted.
    health.AddDelegate<ThirdPartyEmailClient>("EmailProvider",
        client => client.IsConnected
            ? HealthStatus.Healthy
            : new HealthEvaluation(HealthStatus.Unhealthy, "SMTP refused"));

    // Define composite aggregation nodes.
    // Use nameof for concrete types, constants for virtual nodes.
    health.AddComposite(ServiceNames.Application, app =>
    {
        app.DependsOn<AuthService>(Importance.Required);
        app.DependsOn(ServiceNames.NotificationSystem, Importance.Important);
    });

    // Composites with custom aggregation strategies.
    health.AddComposite(ServiceNames.DatabaseCluster, db =>
    {
        db.DependsOn(nameof(PrimaryDb), Importance.Required);
        db.DependsOn(nameof(ReplicaDb), Importance.Required);
    }, aggregator: HealthAggregator.AggregateWithRedundancy);

    health.AddRoots(ServiceNames.Application);
    health.UseMonitor(TimeSpan.FromSeconds(30));
});

// Central constants for virtual/composite service names.
static class ServiceNames
{
    public const string Application = nameof(Application);
    public const string NotificationSystem = nameof(NotificationSystem);
    public const string DatabaseCluster = nameof(DatabaseCluster);
}

Declare dependency edges on classes you own with attributes:

[DependsOn<DatabaseService>(Importance.Required)]
[DependsOn<CacheService>(Importance.Important)]
class AuthService : IHealthAware
{
    public HealthNode Health { get; } = new HealthCheck("AuthService");
}

Inject HealthGraph to access the materialized graph at runtime:

var graph = serviceProvider.GetRequiredService<HealthGraph>();
var report = graph.CreateReport();

// Type-safe lookup — uses typeof(AuthService).Name as key.
if (graph.TryGetService<AuthService>(out var auth))
    Console.WriteLine($"AuthService has {auth.Dependencies.Count} deps");

// String-based lookup still available.
var dbService = graph["Database"];

Reactive extensions

The Prognosis.Reactive package provides Rx-based alternatives to polling:

var roots = new HealthNode[] { app };

// Timer-driven polling — emits HealthReport on change.
roots.PollHealthReport(TimeSpan.FromSeconds(30))
    .Subscribe(report => Console.WriteLine(report.OverallStatus));

// Push-triggered — reacts to leaf StatusChanged events, throttled.
roots.ObserveHealthReport(TimeSpan.FromMilliseconds(500))
    .Subscribe(report => Console.WriteLine(report.OverallStatus));

// Diff-based change stream — composable with any report source.
roots.PollHealthReport(TimeSpan.FromSeconds(30))
    .SelectServiceChanges()
    .Subscribe(change =>
        Console.WriteLine($"{change.Name}: {change.Previous} → {change.Current}"));

Sharing streams across subscribers

The Rx helpers produce cold observables — each subscription runs its own pipeline. To share a single evaluation across multiple subscribers:

// Auto-stop when last subscriber unsubscribes.
var shared = roots.CreateSharedReportStream(TimeSpan.FromSeconds(30));

// Replay latest report to late subscribers.
var shared = roots.CreateSharedReportStream(TimeSpan.FromSeconds(30),
    ShareStrategy.ReplayLatest);

// Push-triggered variant.
var shared = roots.CreateSharedObserveStream(TimeSpan.FromMilliseconds(500));

Or use standard Rx multicast operators directly: Publish().RefCount() or Replay(1).RefCount().

Serialization

Both enums use [JsonStringEnumConverter] so they serialize as "Healthy" / "Degraded" / etc. The HealthReport and HealthSnapshot records are designed as wire-friendly DTOs:

{
  "Timestamp": "2026-02-13T18:30:00+00:00",
  "OverallStatus": "Healthy",
  "Services": [
    { "Name": "Database.Connection", "Status": "Healthy", "DependencyCount": 0 },
    { "Name": "Database.Latency", "Status": "Healthy", "DependencyCount": 0 },
    { "Name": "Database.ConnectionPool", "Status": "Healthy", "DependencyCount": 0 },
    { "Name": "Database", "Status": "Healthy", "DependencyCount": 3 },
    { "Name": "AuthService", "Status": "Healthy", "DependencyCount": 2 }
  ]
}

Project structure

Core (Prognosis)

File Purpose
HealthNode.cs Abstract base class — Name, Dependencies, Evaluate(), StatusChanged, NotifyChanged(), DependsOn()
IHealthAware.cs Marker interface — implement on your classes with a single HealthNode Health property
HealthStatus.cs HealthyUnknownDegradedUnhealthy enum
HealthEvaluation.cs Status + optional reason pair, with implicit conversion from HealthStatus
Importance.cs Required, Important, Optional enum
HealthDependency.cs Record linking a HealthNode with its importance
AggregationStrategy.cs Delegate type for pluggable propagation rules
HealthTracker.cs Internal composable helper — dependency tracking, aggregation, and observability
HealthCheck.cs Wraps a Func<HealthEvaluation> — use for services with intrinsic health checks
HealthGroup.cs Pure aggregation point — health derived entirely from dependencies
HealthAggregator.cs Static helpers — Aggregate, AggregateWithRedundancy, EvaluateAll, CreateReport, DetectCycles, Diff, NotifyGraph
HealthReport.cs Serialization-ready report DTO
HealthSnapshot.cs Serialization-ready per-service snapshot DTO
StatusChange.cs Record describing a single service's status transition
HealthReportComparer.cs IEqualityComparer<HealthReport> for deduplicating reports
HealthMonitor.cs Timer-based poller with IObservable<HealthReport>

Reactive extensions (Prognosis.Reactive)

File Purpose
HealthRxExtensions.cs PollHealthReport, ObserveHealthReport, SelectServiceChanges
HealthRxShared.cs CreateSharedReportStream, CreateSharedObserveStream, ShareStrategy

Dependency injection (Prognosis.DependencyInjection)

File Purpose
ServiceCollectionExtensions.cs AddPrognosis entry point — assembly scanning and graph materialization
PrognosisBuilder.cs Fluent builder — ScanForServices, AddDelegate<T>, AddComposite, AddRoots
DependencyConfigurator.cs Fluent edge declaration — DependsOn<T>, DependsOn(name)
DependsOnAttribute.cs [DependsOn<T>] attribute for declarative dependency edges
HealthGraph.cs Materialized graph container — Roots, indexer, CreateReport()
PrognosisMonitorExtensions.cs UseMonitor extension + IHostedService adapter

Requirements

  • .NET Standard 2.0 or .NET Standard 2.1 compatible runtime (.NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+)
  • System.Text.Json (bundled as a dependency)
  • Microsoft.Bcl.AsyncInterfaces (netstandard2.0 only, bundled as a dependency)
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Prognosis:

Package Downloads
Prognosis.DependencyInjection

Microsoft.Extensions.DependencyInjection integration for Prognosis. Provides assembly scanning, fluent graph builder, and IHostedService-based health monitoring for the Prognosis service health graph.

Prognosis.Reactive

System.Reactive extensions for Prognosis. Provides idiomatic Rx operators for polling, merging, and composing health status streams from the Prognosis service health graph.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.1 61 2/19/2026
3.0.0 63 2/19/2026
3.0.0-beta 70 2/18/2026
3.0.0-alpha 69 2/18/2026
2.1.0 164 2/16/2026
2.0.0 163 2/15/2026
1.0.0 170 2/14/2026
1.0.0-alpha 79 2/14/2026