Skip to content

SAIF.NetCore.EnvironmentClient business rules

This page catalogs every business rule this library enforces, identified by stable rule IDs referenced from operations, configuration, security, and API reference pages.

BR-EC-01 The Environment API base URL must be present at construction

Field Value
Statement The OS environment variable EnvironmentApiUrl must be set. It is the Environment API base URL.
Trigger Client construction
Enforcement BaseEnvironmentClient.ServiceUrl field initializer; BaseEnvironmentClient.Setup
Outcome MissingEnvironmentUrlVariableException if null or empty
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Initialize environment client

BR-EC-02 The track/host map must resolve to at least one track

Field Value
Statement The track/host map must be retrieved and contain at least one track. Empty counts as failure.
Trigger Setup
Enforcement BaseEnvironmentClient.GetHostsByTrack
Outcome EnvironmentClientException with the Environment API URL and accumulated error log
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Initialize environment client

BR-EC-03 Track retrieval is retried with linear back-off

Field Value
Statement Track retrieval is retried EnvironmentClient:GetHostsByTrack:RetryCount times (default 3). A sleep of attempt × RetrySleepMs (default 3000 ms) follows every failed attempt, including the final one, before the next attempt or the terminal exception — e.g. 3 + 6 + 9 = 18 s total with defaults, not just the delays between attempts.
Trigger Failure in BR-EC-02
Enforcement BaseEnvironmentClient.GetHostsByTrackWithResilience; GetHostsByTrackConfig
Outcome Final EnvironmentClientException with the accumulated log; a RetryCount ≤ 0 means no attempt is made and the exception is thrown immediately with an empty log*
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs; src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/ClientConfigs/GetHostsByTrackConfig.cs
Operations Initialize environment client

BR-EC-04 A config-file host override requires AD override permission and group membership

Field Value
Statement A config-file host override is considered only when AppSettings:EnvApiDeveloperOverrideAllowed is not false and the computer is a member of AD group Developer_Machine_Users.
Trigger Host resolution
Enforcement HostResolver.IsConfigOverrideAllowed
Outcome false → skip config override
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs
Operations Resolve host, Initialize environment client

BR-EC-05 A non-empty ApplicationHost override sets the host and marks AppConfigOverride

Field Value
Statement When the override is allowed, a non-empty AppSettings:ApplicationHost sets the host and marks AppConfigOverride = true.
Trigger Host resolution
Enforcement HostResolver.TryResolveHostFromAppConfig
Outcome Host = configured value (lowercased)
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs
Operations Resolve host

BR-EC-06 Otherwise the host is inferred from the content root path

Field Value
Statement Otherwise the host is the first known track host that appears as an exact segment of the application content root path.
Trigger Host resolution
Enforcement HostResolver.TryResolveHostFromFilePath
Outcome Host = matched path segment
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs
Operations Resolve host

BR-EC-07 Otherwise the host defaults to the machine name

Field Value
Statement Otherwise the host defaults to the lowercased machine name, and DefaultedToMachineName = true.
Trigger Host resolution
Enforcement HostResolver.ResolveHost
Outcome Host = machine name
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs
Operations Resolve host

BR-EC-08 The resolved host must belong to a known track

Field Value
Statement The resolved host must belong to a track. The track is the (last) track whose host list contains the host.
Trigger Setup
Enforcement BaseEnvironmentClient.ResolveEnvironmentTrack
Outcome EnvironmentClientException("No environment track found for host! Host value: {ApplicationHost}") if no track matches
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Initialize environment client

BR-EC-09 A developer override file is honored only when it exists and the machine is a developer machine

Field Value
Statement A developer settings override file (AppSettings:EnvApiDeveloperSettingsOverride) is honored only if it exists and IsDeveloperMachine() is true. Its entries take precedence over Environment API values for non-secure lookups.
Trigger Setup; each lookup
Enforcement BaseEnvironmentClient.ResolveDeveloperSettingsOverride, TryGetDeveloperOverride, GetSettingFromDictionary
Outcome Override value returned ahead of the Environment API cache
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Initialize environment client, Get setting value, Developer override lookup

BR-EC-10 Environment API requests identify the caller by host and application name

Field Value
Statement Requests to the Environment API identify the caller as client NetCore.EnvironmentClient with the resolved ApplicationHost and ApplicationName. The pre-resolution call uses the machine name.
Trigger Setup
Enforcement BaseEnvironmentClient.Setup
Outcome Header/metadata semantics belong to the IHttpJsonClient implementation (unknown here)**
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Initialize environment client

BR-EC-11 A section-less setting request resolves in the application's track section

Field Value
Statement A setting requested without a section is resolved in the application's track section ({track}.{key}).
Trigger Get setting
Enforcement BaseEnvironmentClient.GetConfigSetting[Async]
Outcome Track-scoped value
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Get setting value

BR-EC-12 Setting keys and section names are lowercased on lookup

Field Value
Statement BuildDictionaryKey lowercases the entire composed lookup key, so a request key's casing never matters on the lookup side. However, StoreSettingInDictionary lowercases only the section when storing an entry — the setting-key portion is stored exactly as the Environment API returned it (ConfigKey.Key, unmodified). Lookups are therefore guaranteed case-insensitive for the section but not for the key: a stored key whose casing differs from the lowercased request will not match*.
Trigger Get/has setting
Enforcement BaseEnvironmentClient.BuildDictionaryKey, GetSectionKey, StoreSettingInDictionary
Outcome Lowercased lookup key; stored keys lowercase only the section part, not the setting key*
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Get setting value, Load section, Has setting

BR-EC-13 Each section is loaded from the Environment API at most once per cache generation

Field Value
Statement A section is recorded as loaded in Sections only after a fetch succeeds and returns one or more settings; such a successfully-loaded, nonempty section is then skipped (not re-fetched) on every subsequent LoadSection[Async] call until the next global refresh, i.e. at most once per cache generation. A zero-result response is never marked loaded — it is explicitly removed from Sections (self-healing), so it is retried on the very next call, not gated by the global refresh. A thrown exception during a per-section reload (performed by Refresh check and cache reload for every section already in Sections) is not caught at that point: Sections is left unchanged, so a section that was already marked loaded before the refresh remains marked loaded — with no matching Settings entries, because Settings was already cleared — until the next global refresh clears and reloads it again. The root/empty section is never recorded in Sections, so it has no "loaded" state and is reloaded from the Environment API on every call that reaches it, even within the same cache generation.
Trigger Get/has/section setting
Enforcement BaseEnvironmentClient.LoadSection[Async], HandleGetConfigurationSettingsResults
Outcome Cached section reused
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Load section

BR-EC-14 Cache refresh is throttled to at most every 60 seconds

Field Value
Statement The client checks the Environment API's lastRefreshed timestamp at most once every TimeToWaitSeconds (60 s). If the server timestamp is newer than the client's LastRefreshed, the whole cache is cleared and every known section is reloaded. An empty cache always triggers a load.
Trigger Any lookup
Enforcement BaseEnvironmentClient.TestForRefreshSettings, CheckForUpdates, Setup
Outcome Cache refresh
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Load section, Refresh check and cache reload, Get secure setting

BR-EC-15 Track-section and named-section settings use different URL shapes

Field Value
Statement Track-section settings use /v2/settings/{app}/{host}/value. Other sections use /v2/settings/{app}/{host}/{section}/value.
Trigger Load section
Enforcement BaseEnvironmentClient.GetEnvironmentApiUrl
Outcome URL selection
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Load section

BR-EC-16 A null, failed, or settings-less Environment API response is an error

Field Value
Statement A null, failed, or settings-less Environment API response is an error.
Trigger Load section
Enforcement BaseEnvironmentClient.HandleGetConfigurationSettingsResults
Outcome EnvironmentClientException (one of three messages)
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Load section, Refresh check and cache reload

BR-EC-17 A section with zero returned settings is retried rather than marked loaded

Field Value
Statement A section for which the server returns zero settings is not marked as loaded, and is removed if it was previously marked, so it is retried on the next access (fix for the 2026-07-14 incident).
Trigger Load section
Enforcement BaseEnvironmentClient.HandleGetConfigurationSettingsResults
Outcome Self-healing retry; no exception
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Load section, Refresh check and cache reload

BR-EC-18 A missing non-secure setting returns an empty string from string overloads

Field Value
Statement A missing non-secure setting is returned as an empty string by string overloads (no error).
Trigger Get setting (string)
Enforcement BaseEnvironmentClient.GetSettingValue/GetSettingValueAsync, GetSettingFromDictionary
Outcome ""
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Get setting value

BR-EC-19 A missing setting requested through a generic overload is an error

Field Value
Statement A missing setting requested through a generic <T> overload is an error. Present values are converted with Convert.ChangeType.
Trigger Get setting <T>
Enforcement ConfigSettingExtensions.ThrowExceptionIfNullOnGeneric/GetValue<T>
Outcome EnvironmentClientException("Environment setting does not exist. It must exist when calling the generic implementation of {methodName}.")
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/Extensions/ConfigSettingExtensions.cs
Operations Get setting value

BR-EC-20 The base client must never return secure settings

Field Value
Statement The base (non-secure) client must never return secure settings.
Trigger Any GetSecureSetting* call on BaseEnvironmentClient
Enforcement BaseEnvironmentClient.RetrieveSecureSetting[Async]
Outcome EnvironmentClientException("Operation not allowed! This application is restricted from retrieving secure settings.")
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Get secure setting

BR-EC-21 SystemType overloads derive the system hash from the injected service

Field Value
Statement SystemType overloads derive the system hash from the injected ISystemHashService.
Trigger Secure get by SystemType
Enforcement BaseEnvironmentClient.GetSecureSetting(configKey, SystemType) and GetSecureSetting(configKey, section, SystemType) overloads (generic <T>, non-generic, and their async counterparts)
Outcome Hash computed by the SAIF.NetCore.SystemHash implementation via ISystemHashService.Get
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Get secure setting

BR-EC-22 A secure setting request is scoped by track and authorized by a system hash

Field Value
Statement A secure setting is requested with the track (not the host) and an authorizing systemHashValue.
Trigger Secure get
Enforcement SecureEnvironmentClient.GetSecureSettingUrlPart
Outcome URL built with a query-string hash
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
Operations Get secure setting

BR-EC-23 Secure values are cached per section/key and re-fetched only after a refresh

Field Value
Statement Secure values are cached per "{section}.{key}" and re-fetched only when the non-secure cache's LastRefreshed is later than the cached fetch time. Null fetches are not cached on first fetch.
Trigger Secure get
Enforcement SecureSettingCache.GetSetting/GetFromCache/FetchAndCache (and async equivalents)
Outcome Cached or refreshed value; cache is not partitioned by the system hash presented*
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cs
Operations Get secure setting, Secure setting cache

BR-EC-24 A secure setting is fetched only if a matching non-secure key exists

Field Value
Statement A secure setting is fetched only if a matching (non-secure) key exists in the same section of the settings cache.
Trigger Secure get
Enforcement SecureEnvironmentClient.RetrieveSecureSetting[Async]
Outcome Otherwise default(T), which leads to BR-EC-26
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
Operations Get secure setting

BR-EC-25 Secure fetch failures map to one of three error messages

Field Value
Statement Secure fetch errors: an inner transport exception is rethrown as-is, 404 means "could not find a value", anything else means "Failed to retrieve".
Trigger Secure get
Enforcement SecureEnvironmentClient.GetValueFromResult
Outcome Exception raised matching the failure category
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
Operations Get secure setting

BR-EC-26 A secure setting resolving to null or the type default is an error

Field Value
Statement A secure setting that resolves to null or default(T) is an error.
Trigger Secure get
Enforcement SecureEnvironmentClient.RetrieveSecureSetting[Async]
Outcome EnvironmentClientException("No crypto data found for requested secure setting: {section}.{configKey}")
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
Operations Get secure setting

BR-EC-27 Developer override file errors are silently swallowed

Field Value
Statement Errors reading or parsing the developer override file are silently ignored.
Trigger Each lookup
Enforcement BaseEnvironmentClient.TryGetDeveloperOverride
Outcome Falls through to the normal Environment API-backed value
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Developer override lookup

BR-EC-28 HasSetting reports presence in the loaded cache for the given section

Field Value
Statement HasSetting reports whether the key exists in the loaded cache for the given section (root when the section is empty).
Trigger Has setting
Enforcement BaseEnvironmentClient.HasSetting, CheckForSetting
Outcome bool
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Has setting, Get secure setting

BR-EC-29 A developer machine is one where the config-file host override applied

Field Value
Statement A "developer machine" is one where the config-file host override was applied (AD group membership + AppSettings:ApplicationHost).
Trigger Developer checks
Enforcement BaseEnvironmentClient.IsDeveloperMachine
Outcome bool
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
Operations Is developer machine, Initialize environment client, Get secure setting

BR-EC-30 SecureEnvironmentClient construction requires server enablement or a developer machine

Field Value
Statement SecureEnvironmentClient can be constructed only when configuration key COREAPIENABLED = "1" (typically env var ASPNETCORE_COREAPIENABLED=1 through the host's prefixed env-var provider) or the machine is a developer machine.
Trigger Construction of SecureEnvironmentClient
Enforcement SecureEnvironmentClient..ctor
Outcome EnvironmentClientException("This server is not enabled for CORE APIs."); this runs after base Setup() has already made network calls
Source src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
Operations Initialize environment client, Get secure setting

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.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/ClientConfigs/GetHostsByTrackConfig.cs
  • src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/Extensions/ConfigSettingExtensions.cs