Cocoar.Configuration.Yaml— new opt-in YAML file provider (FromYamlFile) with reactive file-watching. Plain YAML scalars map to JSON types (booleans, numbers, null); quoted and block scalars stay strings.Cocoar.Configuration.Toml— new opt-in TOML file provider (FromTomlFile) with reactive file-watching (Tomlyn dependency). TOML's typed values (string/int/float/bool/datetime/array/table/array-of-tables) map unambiguously to JSON.- dotenv —
FromDotEnv(path)built into the core package (no dependency):KEY=value,#comments, optionalexportprefix, quotes, inline comments, and:/__key nesting; reactive. - INI —
FromIniFile(path)built into the core package (no dependency):[section]headers,key=value,;/#whole-line comments,./:nesting, quote stripping; values containing;/#(e.g. connection strings) are kept; reactive. - Kubernetes ConfigMap / Secret support — opt-in
followSymlinksonFromFile/FromYamlFile/FromDotEnv(andFileSourceProviderOptions.FollowSymlinks). A ConfigMap-mounted file is a symlink whose content is updated by an atomic..datasymlink swap; with this enabled the file is read (its resolved target must still stay within the configured directory — escaping symlinks are rejected) and the atomic swap is detected and hot-reloaded (via Cocoar.FileSystem 2.3.0 symlink-target tracking). Default off — symlinks remain rejected as before.
- Extracted a reusable
FileBackedProviderbase (watching, path/symlink security, debounce, disposal);FileSourceProvideris now a thin subclass (behavior unchanged). - Documented the provider-contract invariants on
ConfigurationProvider/IProviderConfiguration. - Added a parameterless
FromCommandLine().
- Observable provider: fetch no longer hangs on a cold / complete-without-emit source, and no longer leaks its one-shot subscription.
- HTTP provider: dispose the
HttpResponseMessageafter fetch; optionalSseReadIdleTimeoutreconnects a half-open SSE stream. FileSourceProvider.Disposeis race-safe and guards cancellation.
- Dead internal
MicrosoftConfigurationSource*types (theFromMicrosoftSourceAPI was removed in 6.0.0).
Major release. The headline change is the move off .NET 8.
- Dropped .NET 8 support. All packages now multi-target
net9.0andnet10.0(wasnet8.0/net9.0). Consumers must target .NET 9 or later. Microsoft.Extensions.*dependencies moved to the10.0.xline, aligned with .NET 10.10.0.xships a nativenet9.0target, so .NET 9 consumers take no runtime hit.
IConfigurationAccessor.GetRequiredConfig<T>()/GetRequiredConfig(Type)— deprecated since v5; useGetConfig<T>()/GetConfig(Type)(identical throw-on-missing behavior).FromMicrosoftSource(...)— useFromIConfiguration(IConfiguration).X509CertificateGenerator.GenerateAndSave(...)— useGenerateAndSavePfx(...)/GenerateAndSavePem(...).
Cocoar.Configuration.WritableStore.Marten— opt-in Marten (PostgreSQL) WritableStore backend.MartenStoreBackendpersists overrides as oneCocoarConfigDocumentper configuration type;FromMartenStore()(service-backed, Layer-2) resolves theIDocumentStorefrom DI and, combined with.TenantScoped(), gives database-per-tenant configuration (each tenant's overlay in its own database).- WritableStore
PatchAsync—IWritableStore<T>.PatchAsync(b => b.Set(...).SetSecret(...).Reset(...))applies any number of mutations as one atomic write and one recompute; single-valueSetAsync/SetSecretAsync/ResetAsyncdelegate to it.
- Configuration layer merging is now case-insensitive on property names (via Cocoar.Json.Mutable 1.2.0), consistent with how the effective config is read back.
- Resetting a secret-typed member no longer throws
NotSupportedException(removing an override exposes no plaintext).
- WritableStore provider — a writable, application-controlled override layer for overridable defaults: the normal sources (files, environment, …) supply defaults, and the application overrides individual values at runtime.
IWritableStore<T>(type-safe facade) andIWritableStoreOverlay<T>(raw key-path surface) inCocoar.Configuration.Abstractions- Sparse writes —
SetAsync(x => x.Smtp.Port, value)persists only the touched leaf; unset keys keep inheriting from the lower layers ResetAsync(...)removes an override (value falls back to the inherited default); an explicitnulloverride is distinct from resetDescribeAsync()returns per-key provenance (StoreEntry: base value, effective value,IsSet) for management UIs.FromStore()rule extension; file-based backend by default with a pluggableIStoreBackendIWritableStore<T>/IWritableStoreOverlay<T>are DI-injectable (single shared singleton) — write your own endpoints with your own validation/normalization/logging- WritableStoreExample example project
IProviderServiceRegistrationnow supports resolve-time factory registrations (ProviderServiceRegistration.Singleton(type, factory)) in addition to eager instances- Multi-Tenancy — the same configuration type resolves to different values per tenant, layered on a shared global base (ADR-005)
ITenantConfigurationAccessorlifecycle onConfigManager:InitializeTenantAsync/EnsureTenantInitializedAsync/IsTenantInitialized/RemoveTenantAsync.TenantScoped()rule marker +TenantonIConfigurationAccessor(default-interface member, non-breaking) — author one flat rule list, no second surface- Per-tenant access:
GetConfigForTenant<T>/GetReactiveConfigForTenant<T>/GetFeatureFlagsForTenant<T>/GetEntitlementsForTenant<T>/GetWritableStoreForTenant<T> - Tenant-only types are excluded from the global DI plan (avoids the captive-dependency bug); per-tenant flags/entitlements need no source-generator change
- Tenant config consumption (DI, no ASP.NET dependency): scoped
ITenantReactiveConfig<T>+ITenantContext;AddCocoarTenantResolver<TService>(s => s.TenantId)resolves the current tenant from any DI service (HTTP viaIHttpContextAccessor) — no hand-written adapter - ASP.NET Core:
MapTenantFeatureFlagEndpoints()/MapTenantEntitlementEndpoints()
- Service-Backed (DI-aware) configuration — a two-layer model so config providers can use DI-managed services (ADR-006)
UseServiceBackedConfiguration(...)(DI package) — Layer-2 rules whose provider factories receive the applicationIServiceProviderFromStore((sp, a) => IStoreBackend),FromHttp((sp, a) => HttpClient), andFromService<TService>(s => config)overloads- providers can use
IHttpClientFactory/ Marten / EF without giving up the no-DI core; activated on host start viaIHostedLifecycleService(a recompute, never a rebuild) - public
ServiceBackedProviderBuilder<T>seam so third-party provider packages can author their own(sp, a)overloads - ServiceBackedConfig example project
- Secrets encryption-key publishing — publish the public half of the configured secrets encryption key so a browser/CLI producer can build
cocoar.secretenvelopesISecretEncryptionKeyProvider(GetCurrentKey()/GetCurrentKeyForTenant(tenantId)) returns exactly one current public key — the newest cert (per the configured comparer); older certs stay decrypt-only for rotation- ASP.NET Core
MapSecretEncryptionKey()(single-tenant) andMapTenantSecretEncryptionKey()(per-tenant; tenant fromITenantContext) at/.well-known/cocoar/encryption-key— one key per request, never a list, no cross-tenant exposure SecretEnvelope<T>for typed secret-overlay writes; WritableStoreSetSecretAsync/SetSecretEnvelopeAsyncaccept pre-encrypted envelopes
- Public
ProviderObservable/ProviderDisposablehelpers (inCocoar.Configuration.Providers.Abstractions) for authoring a custom provider's change stream without referencing System.Reactive FromFile(a => …)config-aware file-path overload (resolves the path from the accessor per recompute) — the natural shape for per-tenant file rules
- Secret payloads (the decrypted value of
Secret<T>) now (de)serialize with lenient options: enums as names (round-trip-safe if the enum is later reordered) and case-insensitive property matching. Reading still accepts numeric enums and any casing, so existing encrypted secrets remain fully readable — no migration. Only the in-memory form of newly serialized typed secret values changes (enum name instead of ordinal); encrypted envelopes at rest are unaffected. - Reading a tenant-only type (every rule
.TenantScoped()) from the global pipeline now throws a targeted error pointing atGetConfigForTenant<T>(id)/GetReactiveConfigForTenant<T>(id)— for bothGetConfig<T>()andGetReactiveConfig<T>()— instead of the misleading generic "no configuration rule is registered" message (a rule does exist; it is just tenant-scoped). Matches the existing mixed-scope-tuple guard.
- Secret-typed members (
Secret<T>/ISecret<T>) cannot be overridden via WritableStore — the typed facade throwsNotSupportedException(manage secrets via the Secrets CLI/provider). - Overlay values serialize with vanilla options (enums as strings) and overlay keys are aligned to the lower layers' casing, so an override replaces the base key rather than creating a casing-variant sibling.
- .NET 8 LTS support — all library packages multi-target net8.0 and net9.0
- Feature Flags & Entitlements framework (
FeatureFlag<T>,Entitlement<T>,IFeatureFlags<TConfig>,IEntitlements<TConfig>) - Source generator for feature flag/entitlement classes (produces constructor and
Configproperty) IFeatureFlagEvaluator/IEntitlementEvaluatorfor contextual evaluation (Scoped)IContextResolver<TRequest, TContext>for bridging HTTP requests to domain context- REST evaluation endpoints:
MapFeatureFlagEndpoints(),MapEntitlementEndpoints() - Flag expiry tracking and health degradation (
ExpiresAt,IFeatureFlagsDescriptors) IFlagsHealthSourcefor flags contributing to health status- SSE (Server-Sent Events) support in HTTP provider (
serverSentEvents: true) - One-time HTTP fetch mode (no polling, no SSE)
FromIConfiguration(IConfiguration)— simplified Microsoft adapter APIFromHttp()— simplified HTTP provider API with simple overload- Resolver registration via
[]collection expressions andResolverBuilder - Resolver lifetime customization (
.AsSingleton(),.AsScoped(),.AsTransient()) via Capabilities - OpenTelemetry metrics:
cocoar.config.health.status,cocoar.config.recompute.count,cocoar.config.recompute.duration,cocoar.config.provider.errors,cocoar.config.flags.evaluations - Activity source
Cocoar.Configurationfor distributed tracing - ASP.NET Core health check integration (
AddCocoarConfigurationHealthCheck()) where T : classconstraint onTypedRuleBuilder<T>— configuration types must be reference types- Roslyn analyzer diagnostics: COCFLAG001 (non-static ExpiresAt), COCFLAG002 (abstract type registered), COCFLAG003 (missing description)
- File provider security: symlink/reparse point rejection, improved path traversal defense
- VitePress documentation site with complete guide, reference, and roadmap sections
llms.txtandllms-full.txtexport for LLM consumption- How-To guide: Migrating from IOptions
- Certificate management guide
ConfigManager.Create()static factory with fluent builder patternConfigManager.CreateAsync()async factory withCancellationTokensupportUseSecretsSetup()builder extension for secrets configuration- Testing API:
ReplaceConfiguration(),AppendConfiguration(),ReplaceSecretsSetup()with fluentTestOverrideBuilder - Aggregate Rules:
FromFiles(params string[])for concise file layering,.Aggregate(r => [...])for general-purpose rule grouping AggregateRuleManager— isolated execution boundary for grouped rules (inner Required stays within aggregate)TypedProviderBuilder<T>base class for provider extension methods (prevents recursive nesting in aggregates)IRuleManagerinterface extracted fromRuleManagerfor uniform engine handlingSubManagersproperty onIRuleManagerfor ConfigHub drill-down into aggregate structure- ADR-004: Aggregate Rules with Isolated Execution Boundary
- AggregateRules example project
Flag<T>renamed toFeatureFlag<T>(andFlag<TContext, TResult>toFeatureFlag<TContext, TResult>)- Package
Cocoar.Configuration.HttpPollingrenamed toCocoar.Configuration.Http FromHttpPolling()renamed toFromHttp()FromMicrosoftSource()deprecated in favor ofFromIConfiguration()HttpPollingRuleOptionsreplaced byHttpRuleOptions- Default resolver lifetime changed from Transient to Scoped
- File path resolution now uses
AppContext.BaseDirectory(wasAssembly.GetEntryAssembly().Location) - Health API simplified:
ConfigManager.HealthStatusandConfigManager.IsHealthyproperties (wasGetHealthService()) - Resolver registration moved from Core to DI package
UseFeatureFlags()/UseEntitlements()now use[]collection expression pattern- DI package no longer depends on ASP.NET Core FrameworkReference
- ConfigManager constructors and
Initialize()are nowinternal— useConfigManager.Create()instead AddCocoarConfiguration()now uses the builder API:c => c.UseConfiguration(rule => [...], setup => [...])- Secrets setup moved from
setuplambda to dedicated.UseSecretsSetup()builder method - Runtime recomputes are now fully async (no sync-over-async in the recompute pipeline)
- Package consolidation: 10+ packages reduced to 7 (Secrets, Flags, X509Encryption merged into core; Secrets.Abstractions merged into Abstractions; Flags.Generator merged into Analyzers)
- Testing API:
ReplaceAllRules()renamed toReplaceConfiguration(),AppendTestRules()renamed toAppendConfiguration()
Cocoar.Configuration.Secretspackage (merged intoCocoar.Configuration)Cocoar.Configuration.Secrets.Abstractionspackage (merged intoCocoar.Configuration.Abstractions)Cocoar.Configuration.HttpPollingpackage (renamed toCocoar.Configuration.Http)- System.Reactive dependency (replaced with lightweight internal reactive primitives)
- COCFG004 analyzer diagnostic (enforced by
where T : classconstraint instead) IConfigurationHealthServiceinterface (replaced withConfigManager.HealthStatusproperty)FeatureFlagsSetupBuilder,FlagClassRegistrationBuilder,EntitlementClassRegistrationBuilder(replaced byFlagsBuilder,EntitlementsBuilder,ResolverBuilder)RegisterGlobalContextResolver(),WithContextResolver()(replaced byresolvers.Global<T>(),resolvers.For<T>())WithSetup()testing method (was broken and redundant in v5)
- CLI exit codes now consistent across all commands (0=success, 1=argument, 2=IO, 3=crypto, 4=general)
- File provider symlink escape vulnerability
- Provider consistency for optional rules (always returns
{}, never null) Secret<T>.Open()now deserializes directly from UTF-8 bytes instead of creating an intermediate stringSecretJsonConverterusesJsonElement.Deserialize<T>()instead ofGetRawText()to avoid plaintext string intermediatesX509HybridCrypto.Encrypt()zeros the heap copy of the Data Encryption Key in thefinallyblockConfigSnapshotBuilder.CreateJsonPreview()shows only property names (not values) to prevent secret leakage
// v4.x
var manager = new ConfigManager(
rule => [rule.For<AppSettings>().FromFile("config.json")],
logger: myLogger
).Initialize();
// v5.0
var manager = ConfigManager.Create(c => c
.UseConfiguration(rule => [
rule.For<AppSettings>().FromFile("config.json")
])
.UseLogger(myLogger));See Migration Guide v4→v5 for all patterns.
- Interface reactive configs:
IReactiveConfig<IInterface>now works for interfaces exposed viasetup.ConcreteType<T>().ExposeAs<IInterface>(). Previously, requesting a reactive config for an interface type threw an error. Tuples containing interface types (e.g.,IReactiveConfig<(IAppSettings, IDatabaseSettings)>) also now work correctly.
NEW: Abstractions Packages
Cocoar.Configuration.Abstractions- Lightweight package containing core interfaces for decoupled architectureIConfigurationAccessor- Interface for accessing configuration valuesIReactiveConfig<T>- Interface for reactive configuration (supports tuples for atomic multi-config updates)- Enables libraries to depend on abstractions without taking a dependency on the full configuration implementation
Cocoar.Configuration.Secrets.Abstractions- Lightweight package containing secrets interfacesISecret<T>- Interface representing a secret value that can be openedSecretLease<T>- Struct providing controlled access to secret values with automatic cleanup- Enables code to work with secrets through interfaces for better testability and decoupling
ISecret<T>properties in configuration classes automatically deserialize toSecret<T>instances
NEW: AllowPlaintext() for Secrets
- New
AllowPlaintext()fluent API to conditionally allow plaintext JSON values to be deserialized intoSecret<T>properties - Useful for development and testing scenarios where encrypted envelopes are not available
- SECURITY WARNING: Only enable in development/test environments; production should always use encrypted envelopes
- Example:
.UseSecretsSetup(secrets => secrets.AllowPlaintext(builder.Environment.IsDevelopment()))(v5.0+ syntax)
NEW: Testing Setup Overrides
- Extended
CocoarTestConfigurationto support setup overrides in addition to rule overrides- New
WithSetup()method for setup-only overrides (keeps original rules) - Optional
setupparameter added toReplaceAllRules(),AppendTestRules(), andApply() TestConfigurationContext.Replace()andAppend()factory methods now accept optional setup parameter- Enables test-time setup options like
setup.Secrets().AllowPlaintext()without modifying application code - Setup overrides are always merged (appended) to configured setup, using last-write-wins for capabilities
- See Testing Overrides Documentation for usage patterns
- New
- Deserialization failure logging:
GetConfig<T>()now logs an error (EventId 5100) when deserialization fails due to missingrequiredproperties or type mismatches, instead of silently returningnull. This helps diagnose configuration issues while maintaining backward-compatible behavior (still returnsnull,GetRequiredConfig<T>()still throws). - DI registration ordering: Service descriptors are now emitted in deterministic order (sorted by type full name). Previously, dictionary iteration order could vary between runs, affecting test assertions and diagnostic logging.
- Provider rebuild callback ordering: Fixed callback timing in
RuleProviderLeaseto invoke the subscription reset callback before rebuilding the provider, matching originalRuleManagerbehavior. This prevents spurious change notifications that could occur if a provider'sDispose()triggers events while the subscription is still active.
- Type Relocation with Forwarding: Moved core interfaces to abstractions packages with type forwarding for full backward compatibility
IConfigurationAccessormoved toCocoar.Configuration.AbstractionsIReactiveConfig<T>moved toCocoar.Configuration.AbstractionsSecretLease<T>moved toCocoar.Configuration.Secrets.Abstractions- Existing code continues to work unchanged via
[TypeForwardedTo]attributes
- Provider consistency bug: Optional rules now consistently return empty objects with C# defaults when sources are unavailable, instead of inconsistently returning null. This fixes a bug where source-based providers (File, HTTP) behaved differently than collection-based providers (Environment, CommandLine). See ADR-003 for details.
- All providers now return
{}on failure, resulting in configuration objects with C# property defaults - Failures are tracked via health monitoring with
Degradedstatus - Eliminates need for workarounds like adding fake
FromEnvironment()rules GetConfig<T>()never returns null when rules are defined for type TGetRequiredConfig<T>()now explicitly checks for rule definitions (static check), not runtime availability
- All providers now return
- Removed internal
includeflag fromRuleManager.ComputeAsync()return type - now returnsReadOnlyMemory<byte>?wherenullmeans skip (When condition false), simplifying the API and making the decision point clearer in the recompute cycle
NEW: Testing Configuration Overrides
CocoarTestConfigurationAPI for overriding configuration in integration tests- Two modes:
ReplaceAllRules()(skip original rules) andAppendTestRules()(last-write-wins merging) - Uses
AsyncLocal<T>for automatic test isolation across parallel tests - Works universally with direct
ConfigManagerinstantiation, DI, AspNetCore, andWebApplicationFactory - Zero application code changes required - detection happens in ConfigManager constructors
- Comprehensive documentation in Testing Overrides
- Example project demonstrating usage patterns
NEW: Cocoar.Configuration.Secrets Package [Developer Preview]
Secret<T>type for type-safe secret handling with automatic memory zeroing- Hybrid encryption using RSA key wrapping + AES-GCM for envelope-based secrets
- X.509 certificate-based encryption with password-less certificates (industry standard)
- Security model: File permissions (
chmod 600) + full-disk encryption (BitLocker/LUKS/FileVault) - Follows nginx, PostgreSQL, Kubernetes, Docker patterns
- No password bootstrapping problem
- Security model: File permissions (
- Flexible folder-based key management with certificate inventory
- Support for key identifiers (kid) for multi-tenant scenarios
- Configurable certificate ordering and subdirectory search depth
- Seamless JSON deserialization support via custom converters
- Works with primitives, complex types, collections, and nested objects
[Developer Preview]**
⚠️ Developer Preview: CLI commands and options may evolve based on feedback. NEW: Cocoar.Configuration.Secrets.Cli Tool - Command-line tool for managing encrypted secrets in JSON configuration files
generate-cert: Generate self-signed certificates (PFX or PEM format)- Password-less by default (password optional for legacy compatibility)
- Smart format detection from file extension (
.pfx,.crt,.cer,.pem)
convert-cert: Convert between certificate formats and remove passwords- Supports PFX↔PEM conversion with automatic format detection
- Output password optional - defaults to password-less (industry standard)
- Provides platform-specific file permission guidance
- Unified tool for format conversion and password removal
cert-info: Display detailed certificate information- Shows validity, key size, password status
- Detects password-protected vs password-less certificates
- Validates certificate thumbprints for conversion verification
encrypt: Encrypt plaintext values in JSON filesdecrypt: Decrypt encrypted values from JSON files
NEW: Cocoar.Configuration.Analyzers Package
- Roslyn analyzers for compile-time configuration validation
- COCFG001: Detects secret path conflicts (non-secret properties with same path as secret properties)
- COCFG002: Validates rule dependency ordering (prevents accessing config types not yet loaded)
- COCFG003: Warns about required rules referencing potentially missing resources
- COCFG004: Enforces type safety in configuration accessors
- COCFG005: Identifies duplicate unconditional rules for the same type
- COCFG006: Suggests optimal ordering for static/seed rules vs dynamic rules
BREAKING: Provider contract refactored to use byte[] instead of JsonElement
FetchConfigurationAsync→FetchConfigurationBytesAsync(returnsbyte[])Changes→ChangesAsBytes(emitsbyte[])- Improves performance by avoiding intermediate JsonElement conversions
- All built-in providers updated (File, Http, Environment, CommandLine, MicrosoftAdapter, etc.)
- Public ConfigManager API unchanged - byte[] conversion handled internally
- The provider contract change is internal - consuming applications are not affected
- Secrets feature is in Developer Preview: While production-ready, the API may evolve based on real-world usage and feedback
- Password-less certificates are the recommended approach (industry standard: nginx, PostgreSQL, Kubernetes, Docker)
-
Rule Naming: New
.Named("name")fluent API for adding human-readable names to rules- Example:
rule.For<DbConfig>().FromFile("db.json").Named("Primary Database") - Names appear in health snapshots for better observability in dashboards and logs
- Example:
-
Enhanced Health Monitoring: Expanded
RuleHealthEntrywith additional metadataName- Optional rule name (set via.Named())ProviderType- Name of the provider type used by the ruleConfigType- Name of the configuration type being loadedSkippedstatus - NewRuleResultStatus.Skippedfor rules skipped by.When()conditions
-
Health Summary Metrics: New
Skippedcount inSummaryfor tracking conditional rules- Complements existing
Total,RequiredFailed,OptionalFailedcounters
- Complements existing
-
Auto DI Registration:
IConfigurationHealthServicenow automatically registered in DI container- Available immediately after
AddCocoarConfiguration() - No manual registration needed
- Available immediately after
-
Comprehensive Health Tests: 94 lines of new integration tests covering all health monitoring features
- Health Documentation: Simplified health-monitoring.md with clearer, direct usage examples
- Added Prometheus endpoint example (pull-based metrics)
- Added reactive subscription example (push-based metrics)
- Removed complex export system in favor of simple, direct
IConfigurationHealthServiceaccess
- Experimental Metrics Export APIs (marked "Experimental / Untested" in v3.2.0):
HealthMetricsExporterclass - Unnecessary wrapper around health serviceISimpleHealthMetricsSinkinterface - Over-engineered abstractionHealthMetricsstruct - Redundant withConfigHealthSnapshotAddCocoarHealthMetricsExporter()extension - Removed in favor of direct health service usage- Users should access
IConfigurationHealthService.Snapshotdirectly or subscribe toSnapshotStream
-
CommandLine Provider: New built-in provider for parsing command-line arguments
- Supports multiple switch prefixes simultaneously:
["--", "-", "/"]or custom semantic prefixes like["@", "#", "%"] - Example:
.FromCommandLine(["--", "-", "/"])accepts all three styles in the same command line - Automatic longest-match-first algorithm prevents ambiguity (e.g.,
--hostmatches"--"before"-") - Flexible API:
.FromCommandLine(),.FromCommandLine("prefix_"),.FromCommandLine(["-"]) - Supports nested configuration with
:or__separators (e.g.,--database:host=localhostor--database__host=localhost) - Prefix filtering to map different arguments to different config types
- Three argument formats:
--key=value,--key value,--flag(boolean) - Enables self-documenting CLIs:
invoke.exe @host=server #issue=123 %env=prod
- Supports multiple switch prefixes simultaneously:
-
Test Example: New CommandLineExample project with 6 comprehensive integration tests
- Provider Architecture: Enhanced ProviderOptions vs QueryOptions separation across CommandLine, Environment, and FileSource providers
ProviderOptionsnow only contains provider-level configuration (shared across queries)QueryOptionscontains query-specific parameters (different per rule)- Enables proper provider sharing and rule-specific configuration
- Dead Code Cleanup: Removed 4 unused FileSystemObservable files (~140 lines)
- Existing
FileWatcherObservableis used and battle-tested
- Existing
- Enum String Conversion: Added
JsonStringEnumConverterto support deserializing enums from string values in JSON/environment variables- Fixes issue where enum properties (e.g.,
LogEventLevel.Debug) would fail when provided as strings (e.g.,"Debug") - Common scenario: Visual Studio setting
Logging={"LogLevel":{"Microsoft.AspNetCore.Watch":"Debug"}}as environment variable - Now supports case-insensitive string-to-enum conversion for all enum types
- Fixes issue where enum properties (e.g.,
- Interface Deserialization Support: New
setup.Interface<I>().DeserializeTo<T>()API for deserializing interface-typed properties in configuration classes- Solves the problem where configuration classes with interface properties (e.g.,
public ILoggingConfig Logging { get; set; }) would fail deserialization from JSON sources - Common scenario: Setting logging configuration via environment variables (e.g.,
Logging__LogLevel__Default=Debug) or when Visual Studio injects logging configuration for hot reload - Supports deeply nested interface properties at any depth
- Example:
setup.Interface<ILoggingConfig>().DeserializeTo<LoggingConfig>() - Includes comprehensive test coverage for nested and deeply nested scenarios
- Solves the problem where configuration classes with interface properties (e.g.,
builder.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromEnvironment()
], setup => [
setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>(),
// Map interface properties to concrete types
setup.Interface<ILoggingConfig>().DeserializeTo<LoggingConfig>()
]);- Config-Aware Conditional Rules:
.When()method now receivesIConfigurationAccessorparameter, allowing rules to be conditionally executed based on configuration from earlier rules- Example:
rule.For<PremiumFeatures>().FromFile("premium.json").When(accessor => accessor.GetRequiredConfig<TenantSettings>().Tier == "Premium") - Enables powerful dynamic configuration scenarios (multi-tenant, environment-based, feature flags)
- Example:
- ConditionalRulesExample: New example project demonstrating config-aware conditional rules
- Type-First API: Refactored rule builder API from Provider-First to Type-First pattern for better discoverability and type safety
- Old:
rule.File("...").For<T>()→ New:rule.For<T>().FromFile("...") - All provider methods renamed:
File()→FromFile(),Environment()→FromEnvironment(), etc. - Consistent pattern across all providers (File, Environment, Static, Observable, HttpPolling, MicrosoftSource)
- Old:
- Type-First API Changes (
⚠️ MAJOR):rule.File(...)→rule.For<T>().FromFile(...)rule.Environment(...)→rule.For<T>().FromEnvironment(...)rule.StaticJson(...)→rule.For<T>().FromStaticJson(...)rule.Static<V>(...)→rule.For<T>().FromStatic(...)rule.Observable(...)→rule.For<T>().FromObservable(...)rule.HttpPolling(...)→rule.For<T>().FromHttpPolling(...)rule.MicrosoftSource(...)→rule.For<T>().FromMicrosoftSource(...)rule.FromProvider<P,O,Q>(...)→rule.For<T>().FromProvider<T,P,O,Q>(...)
- When() Signature Change:
.When(Func<bool>)→.When(Func<IConfigurationAccessor, bool>)- Old:
rule.File("...").When(() => condition).For<T>() - New:
rule.For<T>().FromFile("...").When(_ => condition)or with accessor:.When(accessor => accessor.GetRequiredConfig<Other>().Property)
- Old:
- Removed test helper methods from production provider APIs (
CreateRulemethods in FileSourceProvider, StaticJsonProvider, ObservableProvider)- These were internal test utilities mistakenly exposed in public API surface
// v2.0 (Provider-First)
builder.AddCocoarConfiguration(rule => [
rule.File("config.json").Select("App").For<AppSettings>(),
rule.Environment("APP_").For<AppSettings>(),
// Conditional rule (old signature)
rule.File("premium.json")
.When(() => isPremium)
.For<PremiumFeatures>()
], setup => [
setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);
// v3.0 (Type-First + Config-Aware When)
builder.AddCocoarConfiguration(rule => [
rule.For<AppSettings>().FromFile("config.json").Select("App"),
rule.For<AppSettings>().FromEnvironment("APP_"),
// Conditional rule (new signature with accessor)
rule.For<PremiumFeatures>().FromFile("premium.json")
.When(accessor => accessor.GetRequiredConfig<TenantSettings>().Tier == "Premium")
], setup => [
setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);See Migration Guide v2→v3 for detailed migration instructions.
- Builder API Modernization: Replaced static
Rule.From.*API with function-basedRulesBuilderpattern (rule => rule.File(...)) for more intuitive configuration - Setup API Modernization: Replaced
Bind.Type<T>().To<I>()API with function-basedSetupBuilderpattern (setup => setup.ConcreteType<T>().ExposeAs<I>()) with clearer naming - Renamed "binding" terminology to "exposure" throughout the API and documentation for clarity
Rule.From.File(),Rule.From.Environment(), etc. replaced withRulesBuilderlambda parameter:builder.AddCocoarConfiguration(rule => [rule.File(...), rule.Environment(...)])Bind.Type<T>().To<I>()replaced withSetupBuilderlambda parameter:setup => setup.ConcreteType<T>().ExposeAs<I>()ServiceRegistrationOptionsandRegister.Add<T>()replaced with direct lifetime configuration onConcreteTypeSetup
// v1.x
builder.AddCocoarConfiguration([
Rule.From.File("config.json").Select("App").For<AppSettings>(),
Rule.From.Environment("APP_").For<AppSettings>()
], [
Bind.Type<AppSettings>().To<IAppSettings>()
]);
// v2.0
builder.AddCocoarConfiguration(rule => [
rule.File("config.json").Select("App").For<AppSettings>(),
rule.Environment("APP_").For<AppSettings>()
], setup => [
setup.ConcreteType<AppSettings>().ExposeAs<IAppSettings>()
]);IReactiveConfig<T> now supports tuples as the generic type, e.g.
IReactiveConfig<(AppSettings, DbSettings)>.
When used with a tuple, all element types are recomputed and emitted atomically in the same pass.
This guarantees you never see a mix of old and new values across different configs.
This marks the first stable release of Cocoar.Configuration with production-ready features and comprehensive testing infrastructure.
- Comprehensive Test Suite: 204 automated tests covering core functionality, providers, edge cases, and stress scenarios
- Health Monitoring System: Complete health monitoring with
IConfigurationHealthService, health snapshots, and experimental metrics export hooks - Reactive Configuration: Auto-registered
IReactiveConfig<T>for every configuration type in dependency injection - Enhanced Documentation:
- Streamlined README with practical examples and accurate feature descriptions
- Dedicated health monitoring guide (
website/guide/health/overview.md) - Reactive configuration guide (
website/guide/reactive/basics.md)
- Integration Tests: Multi-provider composition, rule ordering, recompute pipeline validation
- Stress & Performance Tests: High-frequency changes, large JSON handling, concurrent access scenarios
- Provider Battle Tests: HTTP headers/caching, Microsoft adapter integration, environment variable edge cases
- Health Pipeline Tests: Status derivation, recovery scenarios, observable health updates
- Fuzz Testing: Random change sequences maintaining correctness and deterministic results
- File Provider Resilience: FileSystemWatcher ↔ polling fallback and automatic recovery testing
- File Provider: Improved resilience with automatic polling fallback when FileSystemWatcher fails
- Test Organization: Restructured test projects with clear separation (Core.Tests, Providers.Tests)
- Example Projects: Updated all 12 example projects with improved clarity and modern patterns
- Documentation Structure: Consolidated scattered documentation into focused, practical guides
- 204 comprehensive tests ensuring stability across all components and failure scenarios
- Production-tested patterns validated through extensive integration and stress testing
- Error-resilient implementations with proper failure handling and recovery mechanisms
- Continuous integration ensuring every change maintains stability and correctness
- FileSystemWatcher File Locking: Fixed file locking conflicts in FileSourceProvider by implementing proper file sharing (FileShare.ReadWrite) to prevent missed configuration changes and IOException conflicts in production scenarios.
- Testing Anti-Patterns: Eliminated flaky test behavior by removing emission counting anti-patterns and implementing proper final state validation in reactive configuration tests.
- Enhanced test organization with better naming conventions and regional structuring for improved maintainability.
- Implemented active waiting patterns and controllable testing infrastructure (ObservableProvider) for reliable FileSystemWatcher testing.
- Added comprehensive stress testing suite with multi-iteration reliability validation.
- StaticJsonProvider now supports JSON strings directly via
Rule.From.StaticJson(jsonString).
- StaticJsonProvider instances are no longer shared between rules (null-key pattern) to prevent configuration data leakage between different rules.
- License changed from MIT to Apache-2.0 to provide an explicit patent grant and consistent downstream attribution via
NOTICE. Previous released versions remain under MIT.
- Reactive configuration channel: automatic
IReactiveConfig<T>for every config type (singleton, hash-gated, error-resilient). - Auto DI registration for
IReactiveConfig<T>(opt-out viaDisableAutoReactiveRegistration).
- Streaming JSON → MD5 hashing pipeline for selection & emission gating (reduced allocations, faster change detection).
- Partial recompute optimizations (earliest changed rule restart) documented and hardened.
- README overhaul: simplified quick start, lifetimes, reactive defaults, full examples table with direct links.
- Added
DEEP_DIVE.md(advanced scenarios, tuning, dynamic dependencies). - Updated architecture doc: streaming MD5 hashing + reactive channel section.
- Clarified partial recompute & reactive channel in Concepts; added migration note for merged reactive implementation.
- Converted inline doc code references to clickable links across docs & examples.
- Refactor configuration options to set default 'Required' value to false and remove unnecessary 'Optional' calls in tests
- Add DI Options for Cocoar.Configuration.AspNetCore - AddCocoarConfiguration
- New Binding System (
Bind.Type<T>().To<IInterface>()) enabling interface mapping independent of DI. BindingRegistryand runtime validation for interface→concrete compatibility.Cocoar.Configuration.DIpackage: separation of concerns between interface binding and DI registration.ServiceRegistrationOptions.DefaultRegistrationLifetime(null)to fully disable auto-registration.- Keyed service registration refinement via explicit
options.Register.Add<T>(lifetime, key)model. - Comprehensive documentation: docs/BINDING.md, updated README Binding vs DI Registration section.
- Auto-registration default clarified (Scoped) and now explicitly optional.
- Examples reorganized: added dedicated Binding, DI, ServiceLifetimes demos; README minimized to one-liners.
- Expanded test coverage for service lifetimes, keyed registrations, binding validation.
- Documentation cleanup removing unreleased prototype terminology.
- For consumers of 0.9.x: follow 0.10.0 migration first (selection API changes), then optionally adopt bindings (purely additive).
- Removed query-level
configurationPathandtargetPath; use rule-level.Select(...)and.MountAt(...).
- Rule Selection Simplification: centralized rule-level selection (
.Select) clarifies intent and simplifies providers. - Incremental Recompute Engine: recompute only from earliest changed rule; unchanged prefix reused from cached flattened contributions.
- Change Gating & Noise Reduction: selection-hash gating skips recompute when the selected subtree is unchanged.
- Fast Cancellation & Coalescing: mid-pass cancellation plus immediate + trailing debounce collapses bursts without added latency for first change.
- Coalescer Extraction: new
RecomputeCoalescerclass isolates change accumulation & timing logic. - Deletion Propagation: removed keys from updated rule contributions are pruned from final configs (no stale "zombie" keys).
- Snapshot Model Simplification: dropped merged/unflattened snapshot storage; now only per-rule flattened contribution + selection hash.
- Static Rule Set Decision: rules immutable post-initialization (use
UseWhento conditionally participate). - Provider & Query Cleanup: normalized option/query shapes; file examples updated to selection chaining.
- Test Suite Expansion: new tests for partial recompute, cancellation, selection hash gating, key deletions; added explanatory headers.
- Performance Foundations: hashing + earliest-index logic lays groundwork for future benchmarks.
- Documentation & Examples Refresh: updated architecture & provider docs to Fetch → Select → Mount → Merge; removed legacy two-argument file section usage.
- API Surface Cleanup: removed obsolete params & helpers; pruned unused hash utilities.
- Reliability & Determinism: provider injection seam enables deterministic tests; strengthened dynamic dependency scenarios.
- Logging & Error Handling Consistency: resilient change-trigger error handling while preserving required/optional semantics.
- Rule.From.File(_ => FileSourceRuleOptions.FromFilePath("appsettings.json", configurationPath: "A:B")).For<MyCfg>().Build();
+ Rule.From.File("appsettings.json").Select("A:B").For<MyCfg>().Build();
- Rule.From.HttpPolling(_ => HttpPollingRuleOptions.FromPath("service.json", configurationPath: "Service")).For<Service>().Build();
+ Rule.From.HttpPolling("service.json").Select("Service").For<Service>().Build();
- Rule.From.File(_ => FileSourceRuleOptions.FromFilePath("base.json", targetPath: "Config:Base"))...
+ Rule.From.File("base.json").MountAt("Config:Base")...- Added: Concise overloads Rule.From.File(...), Rule.From.Environment(...).
- Added: .MountAt fluent API for rule mounting.
- Migration: Replace targetPath: "A:B" with .MountAt("A:B").
Branding / assets update.
- Replaced NuGet package icon (
package-icon.png). - Updated README image.
- Updated GitHub social preview images (
social-preview.png,social-preview-small.png) stored at repo root. - No functional/code changes.
Initial release 🎉
- Deterministic ordered configuration layering (last-write-wins)
- Strongly typed DI (no IOptions)
- Providers: File, Environment, Static, HTTP Polling, Microsoft Adapter
- Dynamic rule factories & atomic snapshot recompute
- DI lifetimes & keyed registrations
- Examples included under
src/Examples/