---
title: SAIF.NetCore.DBConnectionClient 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.DBConnectionClient
reference_type: package
---

# SAIF.NetCore.DBConnectionClient business rules

This page captures business rules enforced by this library.

## BR-DBC-01 Configuration keys source the service URL and endpoint segments

| Field | Value |
| --- | --- |
| **Statement** | The service URL and the three endpoint path segments come from configuration section `DBConnectionClient` (keys `ServiceUrl`, `SqlConnectionEndpoint`, `PostgreSqlConnectionEndpoint`, `OracleConnectionEndpoint`). |
| **Trigger** | Construction |
| **Enforcement** | `ConnectionClient.Setup` reads all four keys through `IEnvironmentClient.GetSettingValue`; keys are declared in `ConnectionClientKeys`. |
| **Outcome** | Values are stored in protected properties (`ServiceUrl`, `SqlEndpoint`, `PostgreSqlEndpoint`, `OracleEndpoint`) used by every subsequent request. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-02 Every request identifies itself via client-info headers

| Field | Value |
| --- | --- |
| **Statement** | Every request identifies itself with client user `"DBConnectionClient"`, the host's `ApplicationHost`, and `ApplicationName`. |
| **Trigger** | Construction |
| **Enforcement** | `ConnectionClient.Setup` calls `IHttpJsonClient.SetClientInfo` with these values. |
| **Outcome** | Identification headers are applied to all subsequent calls. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-03 NTLM authentication, defaulting to process identity

| Field | Value |
| --- | --- |
| **Statement** | Calls to the DBConnectionAPI authenticate with NTLM — current process identity by default, replaced by explicit service-account credentials when `SetupClientCredentials` is called. |
| **Trigger** | Construction; credential setup |
| **Enforcement** | `ConnectionClient.Setup` calls `SetNtlmAuthentication()`; `ConnectionClient.SetupClientCredentials` calls `SetNtlmAuthentication(username, password)`. |
| **Outcome** | NTLM authentication is configured on the HTTP client. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-04 Cache keys are engine-prefixed

| Field | Value |
| --- | --- |
| **Statement** | Cache keys are engine-prefixed: `SQL_{server}_{instance}_{user}`, `POSTGRES_{instance}_{user}`, `ORA_{instance}_{user}`. |
| **Trigger** | Each `Get*Connection` call |
| **Enforcement** | `GetSqlServerConnection`, `GetPostgreSqlServerConnection`, `GetOracleConnection` build the respective key before cache lookup. |
| **Outcome** | Distinct cache entries per database engine and target. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-05 PostgreSQL host is managed client-side

| Field | Value |
| --- | --- |
| **Statement** | PostgreSQL server/host is not managed by the API: lookup uses only instance and user, and the returned template's `{SERVER}` placeholder is replaced by the caller-supplied server. |
| **Trigger** | `GetPostgreSqlServerConnection` |
| **Enforcement** | `ConnectionClient.GetPostgreSqlServerConnection` omits `server` from the cache key and URL path, then substitutes `{SERVER}` in the retrieved template. |
| **Outcome** | The host is injected client-side; the template is cached host-independent, so different hosts sharing instance and user reuse one fetch. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-06 Successful retrievals are cached for instance lifetime; failures are not

| Field | Value |
| --- | --- |
| **Statement** | A successfully retrieved connection string is cached for the life of the instance and never refreshed; failed retrievals are not cached; cache access is serialized. |
| **Trigger** | Any `Get*Connection` call |
| **Enforcement** | `ConnectionClient.GetCachedConnectionString` adds to the cache only after a successful retrieval, under `lock(_connections)`. |
| **Outcome** | One fetch per key per instance; refreshing a cached value requires replacing the `ConnectionClient` instance — restarting the usual singleton-hosted process is the typical way to do this. Rotating the outbound service-account credential (`dbconnectionclient:connectionservicecreds`) does not by itself clear the cache, because that credential only affects the NTLM identity presented on the next uncached request.* |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-07 Backoff configuration must cover all retry attempts

| Field | Value |
| --- | --- |
| **Statement** | `RetrieveBackoffDelays` must have at least `MaxRetrieveAttempts − 1` entries. |
| **Trigger** | Start of every retrieval |
| **Enforcement** | `ConnectionClient.ValidateBackoffConfiguration` checks the invariant before any HTTP call. |
| **Outcome** | `InvalidOperationException` is thrown before any HTTP call if the invariant does not hold. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-08 Bounded retry with fixed backoff

| Field | Value |
| --- | --- |
| **Statement** | At most `MaxRetrieveAttempts` (default 4) of this client's own **outer** attempts are made, with backoff 2s → 3s → 5s between transient failures (maximum ~10 s of outer waiting). This is not an end-to-end bound: with the standard registered `IHttpJsonClient` transport, each outer attempt can itself make up to four inner HTTP requests with up to three additional 1-second inner sleeps, so total attempts, waiting, and elapsed time can exceed these outer figures. See [Retrieve with retry](operations/retrieve-with-retry.md). |
| **Trigger** | Transient failure |
| **Enforcement** | `ConnectionClient.RetrieveConnectionString` loops up to `MaxRetrieveAttempts`, using `_defaultRetrieveBackoffDelays`. |
| **Outcome** | Retry, then throw the last transient `DBClientException` with an "(attempt n/N)" annotation. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-09 401/403/404 are permanent failures

| Field | Value |
| --- | --- |
| **Statement** | HTTP 401, 404, and 403 are permanent for this client's own **outer** loop: the request fails immediately without an outer retry, even if `ErrorException` is set. This outer fail-fast behavior does not guarantee that the server received only one authentication attempt — with the standard registered `IHttpJsonClient` transport, a 401 is also one of its default inner-retry status codes, so the same credentials can already have been re-presented up to three additional times before this outer check ever observes the result. |
| **Trigger** | Non-success response |
| **Enforcement** | `ConnectionClient.RetrieveConnectionString` checks these statuses before the transient-failure test. |
| **Outcome** | `DBClientException` is thrown ("denied access" / "not found" with service message / "access denied"). |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-10 503 or network-level failure is transient

| Field | Value |
| --- | --- |
| **Statement** | A failure is transient when the status is 503, or the request failed with a non-null `ErrorException` (network-level failure). |
| **Trigger** | Non-success response |
| **Enforcement** | `ConnectionClient.RetrieveConnectionString` classifies the outcome and retries per [BR-DBC-08](#br-dbc-08-bounded-retry-with-fixed-backoff). |
| **Outcome** | Retry is attempted; the inner exception is preserved; a null message falls back to "(no details)". |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-11 Other failures and empty bodies fail immediately

| Field | Value |
| --- | --- |
| **Statement** | Any other non-success status (for example, 500 without an exception) and a successful-but-empty body fail immediately. |
| **Trigger** | Response |
| **Enforcement** | `ConnectionClient.RetrieveConnectionString` throws without retry for these cases. |
| **Outcome** | `DBClientException` is thrown ("Failed to retrieve database connection string: {connectionName} (attempt {attempt}/{MaxRetrieveAttempts})", with ". Service Error Message: {ErrorMessage}" appended when present, for the non-success case; "Database connection string is null: {connectionName} (attempt {attempt}/{MaxRetrieveAttempts})" for the empty-body case). |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## BR-DBC-12 Legacy special-character token is decoded

| Field | Value |
| --- | --- |
| **Statement** | The token `ZSPECIALCHAR` in a returned connection string is decoded to the ASCII BEL control character `(char)7`. |
| **Trigger** | Successful retrieval |
| **Enforcement** | `ConnectionClient.HandleLegacyConnectionString` performs a literal string replacement. |
| **Outcome** | The decoded string is returned and cached. |
| **Source** | `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs` |

---

## Notes
- No rules are inherited from a dependency base class: `SAIFException` (ServiceContracts) adds no behavior beyond `Exception` constructors.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Security and authorization](security-authorization.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.

## Sources
- `src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs`
- `src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIFException.cs`
