1. Secrets are not in IConfiguration by default
The most important entry on this page.
If you know the Azure Key Vault configuration provider, you will expect Nexus secrets to appear in IConfiguration the same way. They do not, and the reason is mechanical rather than stylistic.
.NET has no way to mark a configuration value as secret. Once a value is in the configuration tree:
- the parameterless
IConfigurationRoot.GetDebugView()prints it, configuration.AsEnumerable()walks it,- the developer exception page displays it.
GetDebugView(Func<ConfigurationDebugViewContext, string>) overload that can mask values by originating provider - ConfigurationDebugViewContext exposes the ConfigurationProvider - but it only helps if the caller opts into it, and an SDK cannot force that. On the conventional design, whether your Stripe key ends up in a log line or an error page depends on the discipline of every piece of code that touches the configuration root, including code you did not write.
Our path removes the exposure structurally rather than by convention: IOptions<T> does not require IConfiguration, so AddWestyxNexusSecrets<T>() satisfies it through a factory and the value never enters the configuration tree. You still get constructor injection, strong typing and live reload - you just cannot accidentally dump it.
An opt-in escape hatch exists (AddWestyx(..., includeSecrets: true), default off) with a GetNexusSafeDebugView() masking helper. See Secrets binding for exactly what you accept by enabling it.
2. NexusSecretValue was considered and rejected
A wrapper type whose ToString() returns "[REDACTED]" was proposed and turned down. It is a breaking change to GetSecretAsync, and in the case that actually matters - a secret bound into a POCO string property - it provides no protection whatsoever. A half-measure that creates false confidence is worse than no measure.
3. Extension methods live in Microsoft.Extensions.DependencyInjection
Microsoft’s guidance says extension methods should not be placed in that namespace unless you are authoring an official Microsoft package. We deviate knowingly, because the rest of the ecosystem already does: Serilog, Polly and the AWS and Azure SDKs all register their Add* extensions there.
Following the letter of the guidance while every neighbouring package ignores it would mean AddWestyxNexus() fails to appear when you type services. with your usual usings - which is exactly the discoverability problem this release set out to fix.
4. INexusLogger is obsolete but retained
It is marked [Obsolete], not removed. Removing it in the same release that introduces LoggerFactory would break every existing consumer for no benefit; the bridge costs one small adapter class.
If both Logger and LoggerFactory are set, LoggerFactory wins and the SDK logs a warning once at construction. Removal is a separate, later decision.
5. Microsoft.Extensions.Configuration left the core package
This is a breaking change, taken because a console app or worker service should not carry the configuration pipeline it never touches. AddWestyx() now lives in WestyxNexus.Extensions.Configuration.
Migration is one line in your project file - add the package reference. The namespace is still Westyx.Nexus, so no using changes.
6. PooledConnectionLifetime defaults to 2 minutes
SocketsHttpHandler.PooledConnectionLifetime defaults to InfiniteTimeSpan. Because NexusClient is meant to live for the lifetime of the application, that default means its pooled connections are never recycled and it never observes a DNS change - a real scenario behind an ingress or a load balancer.
Two minutes matches the IHttpClientFactory default handler lifetime, so the standalone and dependency-injection paths behave the same. Tune it with NexusConfig.PooledConnectionLifetime; it is ignored when you supply your own HttpClient, because that client’s handler is yours to configure.
7. IHttpClientFactory handler rotation is safe for SSE
Rotating handlers every two minutes looks fatal for a connection that stays open for hours. It is not: the factory only stops handing an expired handler to new requests; a request already in flight keeps its handler until it completes.
Do not “fix” this with SetHandlerLifetime(Timeout.InfiniteTimeSpan) - that reintroduces the stale-DNS problem the factory exists to solve.
8. FeatureManagement maps boolean state only
Only a flag’sis_active state reaches IFeatureManager. Percentage rollout and cohort targeting are evaluated server-side against a user context and are not routed through the bridge.
A targeting-aware flag exposed through a boolean-only bridge would silently evaluate as its untargeted default, which is worse than being unsupported. See Feature management for how to evaluate targeted flags today.
9. Both target frameworks, and the net8.0 clock
The packages target net8.0 and net10.0. .NET 8 goes out of support on 2026-11-10; .NET 10 is supported to 2028-11-14. net8.0 is kept so consumers are not stranded mid-support-window, and will be dropped after that date.
10. File-type secrets are owner-readable only
Materialised file secrets are created with mode0600, set at creation via UnixCreateMode rather than with a chmod afterwards - a chmod-after leaves a window in which the file exists with the process umask applied, typically 0644 in a shared temp directory.
Each client instance also gets its own file path. The path used to be derived only from the secret’s key and value, so two clients holding the same secret resolved to one file and whichever was disposed first deleted it out from under the other.
On Windows there is no equivalent mode; the file inherits the temp directory ACL.