---
title: SAIF.NetCore.WindowsService business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: SAIF Framework Platform Team
last_reviewed: 2026-10-02
library: SAIF.NetCore.WindowsService
reference_type: package
---

# SAIF.NetCore.WindowsService business rules

This page captures business rules enforced by this library.

## WS-BR-01 Host is created once per process

| Field | Value |
| --- | --- |
| **Statement** | The service host is created once per process and kept in a static property; `Run` builds it only when `ServiceHost` is `null`. |
| **Trigger** | `Run(args)` is called. |
| **Enforcement** | `ServiceProgramRunnerBase.Run`, `ServiceHost` (static property). |
| **Outcome** | Reuses the existing host when already set. When reused, `Create`, the startup delay, and the configuration hooks are all skipped. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs |

---

## WS-BR-02 Optional startup delay blocks before hooks and Run

| Field | Value |
| --- | --- |
| **Statement** | When configuration key `DelayStart` (milliseconds, integer) is present, startup blocks for that long after the host is built and before the configuration hooks and `IHost.Run()`. |
| **Trigger** | `Create(args)` runs. |
| **Enforcement** | `ServiceProgramRunnerBase.DelayStartup`: `Thread.Sleep(DelayStart)`. |
| **Outcome** | If absent, there is no delay. If present, startup blocks synchronously on the main thread. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs |

---

## WS-BR-03 Configuration source stack is altered by re-added providers

| Field | Value |
| --- | --- |
| **Statement** | The host reads `appsettings.json` (optional) and environment variables, in addition to and after, the Generic Host defaults, and runs as a Windows Service when started by the SCM. |
| **Trigger** | `CreateHostBuilder(args)` runs. |
| **Enforcement** | `ServiceProgramRunnerBase.CreateHostBuilder`. |
| **Outcome** | The re-added sources override command-line args and `appsettings.{Environment}.json`, because later sources win*. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs |

---

## WS-BR-04 Error-handler hook runs before the logger is configured

| Field | Value |
| --- | --- |
| **Statement** | The error-handler customization hook runs before the logger is configured. |
| **Trigger** | `Configure(host)` runs. |
| **Enforcement** | `ServiceProgramRunnerBase.Configure`. |
| **Outcome** | Errors raised during `ConfigureErrorHandler` are not logged through the SAIF logger, because the logger is not yet configured. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs |

---

## WS-BR-05 Required hosting services must be registered

| Field | Value |
| --- | --- |
| **Statement** | A SAIF Windows Service must have `IWindowsServiceErrorHandler`, `ISaifWindowsServiceLogService`, `IEnvironmentClient`, and `IHttpJsonClient` registered, and the logger is always configured at startup with the environment client and HTTP client. |
| **Trigger** | `Configure(host)` / `ConfigureLogger(host)` runs. |
| **Enforcement** | `ServiceProgramRunnerBase.Configure`/`ConfigureLogger`, using `GetRequiredService`. |
| **Outcome** | Startup fails with `InvalidOperationException` if any required service is missing. Otherwise `SaifWindowsServiceLog.Configure` runs. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs |

---

## WS-BR-06 Worker loop is fixed-delay, not fixed-rate

| Field | Value |
| --- | --- |
| **Statement** | A worker runs its task immediately, then waits `TaskDelayMilliseconds` (default 3000 ms) after each completion before the next run; runs never overlap. |
| **Trigger** | The worker starts. |
| **Enforcement** | `ServiceWorker<T>.ExecuteAsync`, `TaskDelayMilliseconds`. |
| **Outcome** | Sequential runs with a fixed delay between them. The XML doc describing the delay as an "initial run delay" does not match this behavior. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs |

---

## WS-BR-07 Loop stops when the host's stopping token is cancelled

| Field | Value |
| --- | --- |
| **Statement** | The worker loop stops when the host's stopping token is cancelled. |
| **Trigger** | The host stops. |
| **Enforcement** | `ServiceWorker<T>.ExecuteAsync` (and framework-standard `BackgroundService` semantics). |
| **Outcome** | The loop exits, through `TaskCanceledException` if cancellation arrives during the delay. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs |

---

## WS-BR-08 Every task exception goes through error handling

| Field | Value |
| --- | --- |
| **Statement** | No exception from a task run escapes the worker without going through the error-handling routine. |
| **Trigger** | `TaskRunner.Run` throws. |
| **Enforcement** | `ServiceWorker<T>.RunTaskAsync`. |
| **Outcome** | `HandleTaskError(ex)` is always called. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs |

---

## WS-BR-09 Shutdown exemption requires an exact type match and an allowing predicate

| Field | Value |
| --- | --- |
| **Statement** | An exception is exempt from shutdown only if its exact runtime type full name equals a registered `ShutdownExemptExceptionType.Type` and at least one matching entry's `IsAllowed(ex)` returns `true`; subclasses are not matched. |
| **Trigger** | A task error occurs. |
| **Enforcement** | `ServiceWorker<T>.IsExceptionExemptFromShutdown`. Entry validation (non-null, strict `Exception` subclass, default predicate `true`) comes from `SAIF.NetCore.ServiceContracts`'s `ShutdownExemptExceptionType`. |
| **Outcome** | `true` continues the service; `false` shuts it down. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs; src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ShutdownExemptExceptionType.cs |

---

## WS-BR-10 Non-exempt errors shut down the whole service

| Field | Value |
| --- | --- |
| **Statement** | A non-exempt task error shuts down the whole service, allowing `ShutdownTimeoutSeconds` (default 30) for graceful stop. |
| **Trigger** | A task error occurs and is not exempt. |
| **Enforcement** | `ServiceWorker<T>.HandleTaskError`, `ShutdownTimeoutSeconds`. |
| **Outcome** | `ServiceHost.StopAsync(timeout)` is called. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs |

---

## WS-BR-11 Error messages follow a fixed format

| Field | Value |
| --- | --- |
| **Statement** | The error message passed to the handler is `"An error occured in task: {TaskTitle}"` plus a newline plus `ex.Message`, with `"Service will shutdown."` appended when not exempt. |
| **Trigger** | A task error occurs. |
| **Enforcement** | `ServiceWorker<T>.BuildErrorMessage`. |
| **Outcome** | A consistent, overridable message text is produced for every task error. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs |

---

## WS-BR-12 Every error is reported before any shutdown

| Field | Value |
| --- | --- |
| **Statement** | Every task error is reported to `IWindowsServiceErrorHandler.HandleError` before any shutdown, whether exempt or not. |
| **Trigger** | A task error occurs. |
| **Enforcement** | `ServiceWorker<T>.HandleTaskError`. |
| **Outcome** | `WindowsServiceErrorHandler.HandleError` builds the error object, raises the `BeginErrorHandling` event first (default `Handled = false`, `FireSpecialActions = true`), then logs through `ISaifWindowsServiceLogService.Error` only if a subscriber has not set `Handled = true`, then -- only if `FireSpecialActions` is `true` -- fires each registered `ISpecialErrorAction` whose own independent fire-condition predicate allows it, logging any action that fails, and finally raises `ErrorHandled`. All of this completes before `HandleTaskError` evaluates whether to call `StopAsync`. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs; src/SAIF.NetCore.ErrorHandler/SAIF.NetCore.ErrorHandler/SAIF.NetCore.ErrorHandler/WindowsService/WindowsServiceErrorHandler.cs; src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/BeginErrorHandlingEventArgs.cs |

---

## WS-BR-13 Task runner must implement the worker's type parameter

| Field | Value |
| --- | --- |
| **Statement** | A worker's task runner must implement the worker's type argument `T` (`T : IServiceTaskRunner`). |
| **Trigger** | The worker is constructed. |
| **Enforcement** | `ServiceWorker<T>..ctor`, generic constraint on `T`. |
| **Outcome** | `InvalidCastException` is thrown at host start if the object does not implement `T`. |
| **Source** | src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs |

## Notes
- This library has no in-repo consumer and no test project; composition-pattern statements are otherwise unverified against a real consumer in this repository.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Service registration](service-registration.md)
- [API reference](api/index.md)

> **Note:** Statements marked with * were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with ** could not be confirmed; treat as unknown until verified.

## Sources
- `src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService.csproj`
- `src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceProgramRunnerBase.cs`
- `src/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/SAIF.NetCore.WindowsService/ServiceWorker.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ShutdownExemptExceptionType.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/BeginErrorHandlingEventArgs.cs`
- `src/SAIF.NetCore.ErrorHandler/SAIF.NetCore.ErrorHandler/SAIF.NetCore.ErrorHandler/WindowsService/WindowsServiceErrorHandler.cs`
