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

# SAIF.NetCore.ServiceContracts business rules

This page captures business rules enforced by this library. `SAIF.NetCore.ServiceContracts` is a contracts-only assembly, so all twelve rules below are enforced by executable code in this library itself (constructors, validating setters, and fluent option methods) — there is no database or SQL involvement for any rule.

## BR-001 Raw OIDC credential string must not be null

| Field | Value |
| --- | --- |
| **Statement** | The raw credential string passed to construct `OidcAppCredentials` must not be null. |
| **Trigger** | A consumer calls `new OidcAppCredentials(raw)` with `raw == null`. |
| **Enforcement** | `OidcAppCredentials..ctor(string)` throws `ArgumentNullException(nameof(raw))` before attempting to split the string. |
| **Outcome** | Construction fails immediately; no `OidcAppCredentials` instance is produced. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/OidcAppCredentials.cs` |

---

## BR-002 Raw OIDC credentials must be two pipe delimited segments

| Field | Value |
| --- | --- |
| **Statement** | The raw credential string must split into exactly two `\|`-delimited segments — `clientId\|clientSecret` — with the first segment assigned to `ClientId` and the second to `ClientSecret`. |
| **Trigger** | `OidcAppCredentials` construction with a non-null `raw` value. |
| **Enforcement** | `OidcAppCredentials..ctor(string)` splits on `'\|'` and throws `ArgumentException("Raw credential parameter does not contain valid credential data.")` when the split does not yield exactly two parts; empty segments (e.g. `"\|"`) are accepted. |
| **Outcome** | A correctly-shaped string populates both readonly fields; any other shape (0, 1, or 3+ segments) throws and no instance is produced. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/OidcAppCredentials.cs` |

---

## BR-003 Shutdown exempt exception type must not be null

| Field | Value |
| --- | --- |
| **Statement** | A `ShutdownExemptExceptionType` entry must reference a non-null `Type`. |
| **Trigger** | Calling `new ShutdownExemptExceptionType(null)` (the constructor's required `Type t` parameter) or any other assignment of `null` to `Type`. |
| **Enforcement** | The private `Type` setter in `ShutdownExemptExceptionType` throws `InvalidOperationException("Type cannot be null.")`. |
| **Outcome** | Construction fails; the exempt-type entry is never added to `IWindowsServiceErrorHandler.ShutdownExemptExceptionTypes`. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ShutdownExemptExceptionType.cs` |

---

## BR-004 Shutdown exempt type must be a strict Exception subclass

| Field | Value |
| --- | --- |
| **Statement** | A `ShutdownExemptExceptionType` entry's `Type` must be a strict subclass of `System.Exception`; `Exception` itself is rejected because `IsSubclassOf` is strict. |
| **Trigger** | Construction with a non-null `Type` value. |
| **Enforcement** | The private `Type` setter checks `!value.IsSubclassOf(typeof(Exception))` and throws `InvalidOperationException("Type must be subclass of Exception.")` when the check fails. |
| **Outcome** | Only strict subclasses of `System.Exception` can be registered as shutdown-exempt; `typeof(Exception)` is always rejected. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ShutdownExemptExceptionType.cs` |

---

## BR-005 Shutdown exempt type defaults to always allowed

| Field | Value |
| --- | --- |
| **Statement** | When no `isAllowed` predicate is supplied to `ShutdownExemptExceptionType`, every exception of the exempt type is treated as allowed (handled without triggering shutdown). |
| **Trigger** | Construction with `isAllowed == null`. |
| **Enforcement** | `ShutdownExemptExceptionType..ctor` assigns `IsAllowed = isAllowed ?? (ex => true)`. |
| **Outcome** | The exempt-type entry matches unconditionally unless the caller supplies a narrower predicate. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ShutdownExemptExceptionType.cs` |

---

## BR-006 HTTP client option defaults

| Field | Value |
| --- | --- |
| **Statement** | `HttpClientOptions` defaults to clearing headers after each request (`PersistHeadersAfterRequest = false`), a 1000 ms retry delay, retries limited to HTTP 401/502/503, and the `Default` serializer. |
| **Trigger** | `new HttpClientOptions()`. |
| **Enforcement** | Property initializers on `HttpClientOptions` set the four defaults listed in the statement. |
| **Outcome** | Any `IHttpJsonClient` configured with unmodified `HttpClientOptions` behaves per these defaults; interpretation of the values (the retry loop, header clearing) is implemented by `SAIF.NetCore.HttpClients`*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/HttpClientOptions.cs` |

---

## BR-007 PersistHeaders with no argument enables persistence

| Field | Value |
| --- | --- |
| **Statement** | Calling `PersistHeaders()` with no argument turns header persistence on. |
| **Trigger** | `options.PersistHeaders()`. |
| **Enforcement** | `HttpClientOptions.PersistHeaders(bool option = true)` sets `PersistHeadersAfterRequest = option` and returns `this` for fluent chaining. |
| **Outcome** | `PersistHeadersAfterRequest` becomes `true` unless the caller explicitly passes `false`. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/HttpClientOptions.cs` |

---

## BR-008 UseNewtonsoftJson selects the Newtonsoft serializer

| Field | Value |
| --- | --- |
| **Statement** | Calling `UseNewtonsoftJson()` selects `HttpClientSerializer.NewtonsoftJson` for the options instance. |
| **Trigger** | `options.UseNewtonsoftJson()`. |
| **Enforcement** | `HttpClientOptions.UseNewtonsoftJson()` sets `Serializer = HttpClientSerializer.NewtonsoftJson` and returns `this`. |
| **Outcome** | Subsequent serialization by the implementing `IHttpJsonClient` uses Newtonsoft.Json instead of the default serializer*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/HttpClientOptions.cs` |

---

## BR-009 HttpStatusCodeException defaults to text plain content type

| Field | Value |
| --- | --- |
| **Statement** | The default HTTP response content type carried by a newly constructed `HttpStatusCodeException` is `text/plain`; `StatusCode` defaults to `0` until the consumer sets it. |
| **Trigger** | `new HttpStatusCodeException(message[, inner])`. |
| **Enforcement** | `HttpStatusCodeException.ContentType` initializes to `"text/plain"`; `StatusCode` is an uninitialized `int` field. |
| **Outcome** | Error handlers that render the response body as the declared `ContentType` fall back to plain text unless the thrower overrides it*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/HttpStatusCodeException.cs` |

---

## BR-010 Error handling event args default to unhandled with special actions enabled

| Field | Value |
| --- | --- |
| **Statement** | A newly raised `BeginErrorHandlingEventArgs` defaults to `Handled = false` and `FireSpecialActions = true`; a subscriber may set either flag to change the outcome. |
| **Trigger** | `new BeginErrorHandlingEventArgs(ex, container)`, typically raised by an `IWindowsServiceErrorHandler` implementation. |
| **Enforcement** | `BeginErrorHandlingEventArgs` property initializers set the two defaults; no null checks are performed on the constructor arguments. |
| **Outcome** | Error handling proceeds and special error actions fire by default; the effect of the flags is enforced by the error-handler implementation, not by this library*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/BeginErrorHandlingEventArgs.cs` |

---

## BR-011 Special error action result defaults to not executed

| Field | Value |
| --- | --- |
| **Statement** | A `SpecialErrorActionResult` constructed without an explicit status defaults `Status` to `SpecialErrorActionStatus.NotExectued`. |
| **Trigger** | `new SpecialErrorActionResult(title)` (status parameter omitted). |
| **Enforcement** | `SpecialErrorActionResult..ctor(actionTitle, status = default, ex = null)` uses the enum's default value for `status`, which is `NotExectued` (first declared member). |
| **Outcome** | Callers that do not explicitly record an outcome report the action as not executed rather than succeeded or failed. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SpecialErrorActionResult.cs` |

---

## BR-012 Generic HTTP async overloads are prohibited

| Field | Value |
| --- | --- |
| **Statement** | The generic `DeleteAsync<T>`, `GetAsync<T>`, `PatchAsync<T>`, `PostAsync<T>`, and `PutAsync<T>` members of `IHttpJsonClient` are prohibited because they are documented as not thread safe; callers must use the non-generic `Task<IHttpRequestResult>` overloads instead. |
| **Trigger** | A consumer calls one of the five generic async overloads. |
| **Enforcement** | Each generic overload carries `[Obsolete("This method not thread safe and has been depracated. Please use the overload GetAsync(string urlPart, object body).", true)]` (wording specific to each verb) on `IHttpJsonClient`. |
| **Outcome** | Compilation fails for any caller of the five generic overloads; only the non-generic overloads are usable. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IHttpJsonClient.cs` |

---

## Contract-declared rules (enforced by implementers)

The following semantics are declared by this library's XML documentation comments but are **enforced by the implementing libraries**, not by code here. They are not local business rules of `SAIF.NetCore.ServiceContracts` and are listed for traceability only — each implementing library's own published `business-rules.md` is the authoritative source for verifying these.

## CR-001 Result and content deserialization semantics

| Field | Value |
| --- | --- |
| **Statement** | `GetResult<T>` deserializes only when the request succeeded (otherwise returns `default(T)`); `GetContent<T>` deserializes regardless of status. |
| **Contract member** | `IHttpRequestResult.GetResult<T>`, `.GetContent<T>` |
| **Trigger** | A consumer calls `GetResult<T>()` or `GetContent<T>()` on an `IHttpRequestResult` instance returned by an `IHttpJsonClient` call. |
| **Enforcement** | Declared only by XML documentation comments on `IHttpRequestResult` in this library; the conditional-deserialization logic is implemented by `SAIF.NetCore.HttpClients`' `HttpRequestResult` class, not by code in this package. |
| **Outcome** | Callers that use `GetResult<T>` receive `default(T)` for an unsuccessful request instead of an attempted deserialization, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IHttpRequestResult.cs` |

---

## CR-002 Status code and success semantics

| Field | Value |
| --- | --- |
| **Statement** | `StatusCode` is `0` when the request never reached the endpoint; `IsSuccessful` means a 2xx status. |
| **Contract member** | `IHttpRequestResult.StatusCode`, `.IsSuccessful` |
| **Trigger** | A consumer reads `IHttpRequestResult.StatusCode` or `.IsSuccessful` after an `IHttpJsonClient` call completes. |
| **Enforcement** | Declared only by XML documentation comments on `IHttpRequestResult` in this library; populating these properties correctly is implemented by `SAIF.NetCore.HttpClients`, not by code in this package. |
| **Outcome** | Callers can distinguish a transport failure (`StatusCode == 0`) from an HTTP-level failure and use `IsSuccessful` as a 2xx shorthand, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IHttpRequestResult.cs` |

---

## CR-003 System hash expiry validation

| Field | Value |
| --- | --- |
| **Statement** | `RemoveSystemHashSaltAndValidateLifetime` throws when the embedded create time is older than `expireSeconds`. |
| **Contract member** | `ISystemHashService.RemoveSystemHashSaltAndValidateLifetime` |
| **Trigger** | A consumer calls `ISystemHashService.RemoveSystemHashSaltAndValidateLifetime(systemHashValue, expireSeconds, nowBufferSeconds)`. |
| **Enforcement** | Declared only by XML documentation comments on `ISystemHashService` in this library; the salt-removal, lifetime comparison, and throw behavior are implemented by `SAIF.NetCore.SystemHash`, not by code in this package. |
| **Outcome** | Callers receive the de-salted hash value when it is still within its lifetime, or an exception when it has expired, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISystemHashService.cs` |

---

## CR-004 Cached system hash computation

| Field | Value |
| --- | --- |
| **Statement** | `Get(type)` computes the hash once (SHA256 per XML doc) and returns the cached value afterwards. |
| **Contract member** | `ISystemHashService.Get` |
| **Trigger** | A consumer calls `ISystemHashService.Get(SystemType type)`. |
| **Enforcement** | Declared only by XML documentation comments on `ISystemHashService.Get` in this library; the SHA256 computation and single-compute-then-cache behavior are implemented by `SAIF.NetCore.SystemHash`, not by code in this package. |
| **Outcome** | Repeated calls to `Get` with the same `type` return the same cached hash value instead of recomputing it, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISystemHashService.cs` |

---

## CR-005 LogDetails disclosure restriction

| Field | Value |
| --- | --- |
| **Statement** | `SAIFException.LogDetails` are logged but must never be sent back to the client. |
| **Contract member** | `SAIFException.LogDetails` |
| **Trigger** | An implementation library's error handler catches a `SAIFException` (or subclass) carrying a populated `LogDetails` array. |
| **Enforcement** | Declared only by the XML documentation comment on `SAIFException.LogDetails` in this library; withholding this content from the client response is implemented by `SAIF.NetCore.ErrorHandler`, not by code in this package. |
| **Outcome** | `LogDetails` content reaches the log sink but is withheld from the HTTP response body, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIFException.cs` |

---

## CR-006 HTTP error response controls

| Field | Value |
| --- | --- |
| **Statement** | `HttpStatusCodeException.StatusCode`/`ContentType` control the error response the error handler sends. |
| **Contract member** | `HttpStatusCodeException` |
| **Trigger** | Implementation-library code throws an `HttpStatusCodeException` with a non-default `StatusCode` and/or `ContentType`. |
| **Enforcement** | Declared only by the XML documentation comments on `HttpStatusCodeException` in this library; reading these properties and shaping the HTTP response accordingly is implemented by `SAIF.NetCore.ErrorHandler`, not by code in this package. |
| **Outcome** | The client receives the thrower-specified status code and content type instead of a generic error response, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/HttpStatusCodeException.cs` |

---

## CR-007 Logging threshold configuration

| Field | Value |
| --- | --- |
| **Statement** | The logging threshold defaults to the Environment API setting `tracingdefaults.logthreshold`, overridable per-app with `<AppName>.logthreshold`. |
| **Contract member** | `ISaifLogService` |
| **Trigger** | A logging call is made through an `ISaifLogService` implementation. |
| **Enforcement** | Declared only by XML documentation comments and naming convention associated with `ISaifLogService` in this library; reading these Environment API settings and applying the threshold is implemented by `SAIF.NetCore.Logging`, not by code in this package. |
| **Outcome** | Log entries below the configured threshold are suppressed, with the per-app override taking precedence over the default, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISaifLogService.cs` |

---

## CR-008 OIDC setting group

| Field | Value |
| --- | --- |
| **Statement** | `IOidcSettings` values come from the `NetCore_OIDC` Environment API setting group. |
| **Contract member** | `IOidcSettings` |
| **Trigger** | A consumer reads a property on an `IOidcSettings` implementation. |
| **Enforcement** | Declared only by XML documentation comments and naming convention associated with `IOidcSettings` in this library; retrieving the values from the `NetCore_OIDC` setting group is implemented by `SAIF.NetCore.OIDC`'s `OidcSettings`, not by code in this package. |
| **Outcome** | OIDC configuration values are sourced from the `NetCore_OIDC` Environment API setting group rather than another source, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IOidcSettings.cs` |

---

## CR-009 Database connection caller requirements

| Field | Value |
| --- | --- |
| **Statement** | `IDBConnectionClient` requires the calling application to run under an IIS service account to retrieve SQL/Oracle connection strings from DBConnectionsAPI. |
| **Contract member** | `IDBConnectionClient` |
| **Trigger** | A consumer calls an `IDBConnectionClient` member to retrieve a SQL Server or Oracle connection string. |
| **Enforcement** | Declared only by XML documentation comments on `IDBConnectionClient` in this library; the IIS-service-account requirement and the call to DBConnectionsAPI are implemented by `SAIF.NetCore.DBConnectionClient`, not by code in this package. |
| **Outcome** | Calls made outside an authorized IIS service-account context are rejected by DBConnectionsAPI rather than by this contract, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IDBConnectionClient.cs` |

---

## CR-010 Anonymous IIS authentication scheme

| Field | Value |
| --- | --- |
| **Statement** | `IIdentityService.AuthenticationScheme` is `Windows` for anonymous requests in IIS. |
| **Contract member** | `IIdentityService` |
| **Trigger** | A consumer reads `IIdentityService.AuthenticationScheme` for an anonymous (unauthenticated) IIS request. |
| **Enforcement** | Declared only by XML documentation comments and naming convention associated with `IIdentityService` in this library; detecting the anonymous IIS case and returning `Windows` is implemented by `SAIF.NetCore.AuthService`, not by code in this package. |
| **Outcome** | Anonymous IIS requests report `Windows` as their authentication scheme rather than `null` or an empty string, provided the implementing library honors this documented contract*. |
| **Source** | `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IIdentityService.cs` |

---

## Notes
- Rules marked with `*` in their Outcome field were inferred from XML documentation comments or the implementing library's naming conventions rather than directly observed in this library's compiled code, because the interpretation happens in another package.
- [CR-001](#cr-001-result-and-content-deserialization-semantics) through [CR-010](#cr-010-anonymous-iis-authentication-scheme) document declared contract behavior only; their enforcement and runtime verification belong to the implementing library's own `business-rules.md`, named by the Expected enforcer in each rule's Enforcement field above.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.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.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/OidcAppCredentials.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ShutdownExemptExceptionType.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/HttpClientOptions.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/HttpStatusCodeException.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/BeginErrorHandlingEventArgs.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SpecialErrorActionResult.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IHttpJsonClient.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IHttpRequestResult.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISystemHashService.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIFException.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/ISaifLogService.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IOidcSettings.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IDBConnectionClient.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IIdentityService.cs`
