CodeLogic 5.0.33-preview
See the version list below for details.
dotnet add package CodeLogic --version 5.0.33-preview
NuGet\Install-Package CodeLogic -Version 5.0.33-preview
<PackageReference Include="CodeLogic" Version="5.0.33-preview" />
<PackageVersion Include="CodeLogic" Version="5.0.33-preview" />
<PackageReference Include="CodeLogic" />
paket add CodeLogic --version 5.0.33-preview
#r "nuget: CodeLogic, 5.0.33-preview"
#:package CodeLogic@5.0.33-preview
#addin nuget:?package=CodeLogic&version=5.0.33-preview&prerelease
#tool nuget:?package=CodeLogic&version=5.0.33-preview&prerelease
CodeLogic 5
A modular .NET 11 framework for building structured, lifecycle-managed applications with clean separation between core infrastructure, framework orchestration, and library integrations.
Why CodeLogic?
Most .NET apps start with Program.cs and grow organically — services get registered in random order, configuration is scattered, and adding a plugin system means building one from scratch.
CodeLogic gives you a structured lifecycle out of the box:
- Libraries (reusable integrations) and Applications (your business logic) follow the same 4-phase lifecycle: Configure → Initialize → Start → Stop. Dependencies are always resolved in the right order.
- Configuration and Localization are built in — not bolted on. Each library and application gets its own config file, auto-generated on first run with defaults and validation. Config can be changed and reloaded while running — libraries can apply a change to just the instance that changed (e.g. one database out of three) without a restart.
- Plugins are first-class citizens with hot-reload, isolated assembly contexts, and the same lifecycle as libraries.
- Zero external dependencies. CodeLogic itself has no NuGet dependencies — it's pure .NET 11. External integrations (MySQL, S3, SMTP, etc.) are optional library packages in the CodeLogic.* family.
Install
dotnet add package CodeLogic
What's new in 5.0
- .NET 11 and C# 15. CodeLogic 5 targets
net11.0; the 4.9.x line stays available for .NET 10. ResultandResult<T>are unions (Ok | ErrorandT | Error). Aswitchover a result is checked for exhaustiveness by the compiler, and results compare by value.Erroris a closed record with one subtype per kind (NotFoundError,ValidationError,ConflictError,TimeoutError, ...), so switching on the kind is exhaustive too.- Framework events share a closed base (
FrameworkEvent): subscribe once to receive all of them. - Faster logging and events: a background log writer, near-free disabled log levels, and lock-free event publishing.
Result.ThrowIfFailure()andResultFailedException(which carries theError) for code where a failure is a bug: tests, scripts, startup.
All 4.x Result members still compile. See Migrating to 5.0.
Results
Expected failures are values, not exceptions:
Result<User> found = await users.FindAsync(id);
var message = found switch
{
User user => $"Hello {user.Name}",
NotFoundError => "No such user",
Error error => error.Message,
null => "Empty result",
};
// Where a failure is a bug, throw instead:
User admin = (await users.FindAsync(adminId)).ValueOrThrow();
(await users.DeleteAsync(oldId)).ThrowIfFailure();
Quick Start
var result = await CodeLogic.InitializeAsync(opts =>
{
opts.FrameworkRootPath = "data/codelogic";
opts.AppVersion = "1.0.0";
});
if (result.ShouldExit) return;
// Load optional libraries (each is a separate NuGet package)
await Libraries.LoadAsync<MailLibrary>();
await Libraries.LoadAsync<StorageLibrary>();
// Register your application
CodeLogic.RegisterApplication(new MyApplication());
// Boot: Configure → Initialize → Start (in dependency order)
await CodeLogic.ConfigureAsync();
await CodeLogic.StartAsync();
// ... your app runs ...
await CodeLogic.StopAsync();
On first run, CodeLogic creates a data/codelogic/ directory with auto-generated config files for each library and your application. Edit them to configure — no code changes needed.
Lifecycle
Every library and application follows the same 4-phase lifecycle:
InitializeAsync() Load CodeLogic.json, validate, create LibraryManager
RegisterApplication() Declare the consuming app
ConfigureAsync() Run OnConfigureAsync on app + libraries
(generates/loads config and localization files)
StartAsync() Libraries: OnInitializeAsync → OnStartAsync
Application: OnInitializeAsync → OnStartAsync
--- application runs ---
StopAsync() Application OnStopAsync, unload plugins, then stop libraries
Libraries start before the application and stop after it — so your app can always rely on its libraries being available.
Architecture
CodeLogic/
├── src/
│ ├── Core/ # Pure infrastructure — no framework coupling
│ │ ├── Logging/ # ILogger with scoped file-based output
│ │ ├── Configuration/ # Auto-generated JSON config with validation
│ │ ├── Localization/ # Culture-aware string management
│ │ └── Utilities/ # SemanticVersion, StartupValidator, FirstRunManager
│ │
│ ├── Framework/ # Lifecycle orchestration — uses Core
│ │ ├── Libraries/ # ILibrary, LibraryContext, LibraryManager
│ │ ├── Application/ # IApplication, ApplicationContext
│ │ └── Plugins/ # IPlugin, PluginContext, PluginManager (hot-reload)
│ │
│ └── CodeLogic.cs # Static facade + ICodeLogicRuntime (injectable)
│
├── docs/ # Documentation (getting started, articles, API reference)
└── samples/ # Example applications
Key Design Principles
Core is standalone. Logging, Configuration, and Localization have zero dependencies on the framework lifecycle. You can use them in any .NET app without the rest of CodeLogic.
Framework wires Core into lifecycle. LibraryContext and ApplicationContext bundle Core engines and hand them to libraries/apps at the right phase — nothing gets constructed until it's needed.
Static facade, injectable runtime. The CodeLogic static class is a convenience facade. For testing or advanced scenarios, inject ICodeLogicRuntime via DI.
Plugins are first-class. Same lifecycle, same access to config/logging/localization, hot-loadable at runtime, each in its own collectible AssemblyLoadContext.
Building a Library
Libraries are reusable integrations that plug into the CodeLogic lifecycle:
public class MyLibrary : ILibrary
{
public LibraryManifest Manifest { get; } = new()
{
Id = "my.library", Name = "My Library", Version = "1.0.0"
};
public Task OnConfigureAsync(LibraryContext context)
{
context.Configuration.Register<MyLibConfig>();
return Task.CompletedTask;
}
public Task OnInitializeAsync(LibraryContext context)
{
var config = context.Configuration.Get<MyLibConfig>();
// Initialize services using validated config
return Task.CompletedTask;
}
public Task OnStartAsync(LibraryContext context) => Task.CompletedTask;
public Task OnStopAsync() => Task.CompletedTask;
public Task<HealthStatus> HealthCheckAsync() => Task.FromResult(HealthStatus.Healthy("OK"));
public void Dispose() { }
}
Building an Application
public class MyApplication : IApplication
{
public ApplicationManifest Manifest { get; } = new()
{
Id = "myapp", Name = "My App", Version = "1.0.0"
};
public Task OnConfigureAsync(ApplicationContext context)
{
context.Configuration.Register<AppSettings>();
return Task.CompletedTask;
}
public Task OnInitializeAsync(ApplicationContext context)
{
var settings = context.Configuration.Get<AppSettings>();
return Task.CompletedTask;
}
public Task OnStartAsync(ApplicationContext context) => Task.CompletedTask;
public Task OnStopAsync() => Task.CompletedTask;
}
Building a Plugin
Plugins are hot-loadable at runtime — no restart required:
public sealed class MyPlugin : IPlugin
{
public PluginManifest Manifest { get; } = new()
{
Id = "my.plugin", Name = "My Plugin", Version = "1.0.0",
Description = "Does something cool", Author = "You"
};
public PluginState State { get; private set; } = PluginState.Loaded;
public Task OnConfigureAsync(PluginContext context)
{
context.Configuration.Register<MyPluginConfig>();
return Task.CompletedTask;
}
public Task OnInitializeAsync(PluginContext context)
{
var config = context.Configuration.Get<MyPluginConfig>();
return Task.CompletedTask;
}
public Task OnStartAsync(PluginContext context) { State = PluginState.Started; return Task.CompletedTask; }
public Task OnUnloadAsync() { State = PluginState.Stopped; return Task.CompletedTask; }
public Task<HealthStatus> HealthCheckAsync() => Task.FromResult(HealthStatus.Healthy("OK"));
public void Dispose() { }
}
Load it with pluginManager.LoadPluginAsync(pathToDll); see Plugins.
Official Library Packages
The CodeLogic.* library family provides production-ready integrations:
| Package | Description |
|---|---|
| CodeLogic.Common | Shared utilities: hashing, caching, scheduling, resilience, compression |
| CodeLogic.Imaging | Image editing: resize, crop, rotate, filters, watermarks, format conversion |
| CodeLogic.Data | Unified database framework: LINQ queries, repositories, joins, transactions, schema sync, migrations, backups |
| CodeLogic.Data.MySql / .PostgreSql / .SqlServer / .Sqlite | The database providers for CodeLogic.Data |
| CodeLogic.Mail | SMTP/IMAP email with template engine |
| CodeLogic.Storage | Provider-neutral mounted storage for local filesystems, S3/R2/MinIO, FTP/FTPS, SFTP, WebDAV, Azure Blob, Google Cloud Storage, and OpenStack Swift |
| CodeLogic.SocialConnect | Discord webhooks + Steam Web API |
| CodeLogic.NetUtils | DNS, DNSBL, IP geolocation |
| CodeLogic.GameNetQuery | Game server queries (Valve RCON, Source UDP, Minecraft) |
| CodeLogic.SystemStats | Cross-platform CPU/memory/process monitoring |
| CodeLogic.GitHelper | Git repository management |
| CodeLogic.TwoFactorAuth | TOTP 2FA + QR code generation |
In 5.x, CodeLogic.Data replaces CodeLogic.MySQL2, CodeLogic.MSSQL, CodeLogic.PostgreSQL and CodeLogic.SQLite, which stay on the 4.9.x line.
Each library follows the same lifecycle pattern — load it with Libraries.LoadAsync<T>(), configure it via its auto-generated JSON config file, and use it.
For storage, see the complete CL.Storage guide
for named multi-provider connections, configuration, transfers, metadata, tags, versions, signed URLs,
health checks, and native-provider access. Existing CodeLogic.StorageS3 users can follow the
migration guide.
Requirements
- .NET 11 SDK or later. CodeLogic 5 targets
net11.0only; on .NET 10, stay on CodeLogic 4.9.x (migrating to 5.0) - No external NuGet dependencies (the core framework is self-contained)
Documentation
- Getting Started
- Migrating to 5.0
- Results and Errors
- Application Lifecycle
- Library Lifecycle
- Plugins
- Configuration
- Localization
- Event Bus
- Health Checks
- CLI Arguments
License
MIT — see LICENSE
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0 is compatible. |
-
net11.0
- No dependencies.
NuGet packages (21)
Showing the top 5 NuGet packages that depend on CodeLogic:
| Package | Downloads |
|---|---|
|
CodeLogic.MySQL2
MySQL with typed LINQ, SQL aggregation, projection pushdown, compiled materializers, covering indexes, retention, and a working cache. |
|
|
CodeLogic.NetUtils
DNSBL blacklist checking and IP geolocation services for CodeLogic 5 applications |
|
|
CodeLogic.GitHelper
Git repository management library for CodeLogic 5 applications |
|
|
CodeLogic.Mail
SMTP/IMAP email library with template engine for CodeLogic 5 applications |
|
|
CodeLogic.TwoFactorAuth
TOTP 2FA with QR code generation for CodeLogic 5 applications |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.34-preview | 1 | 11/1/2026 |
| 5.0.33-preview | 136 | 10/31/2026 |
| 5.0.32-preview | 27 | 10/31/2026 |
| 5.0.31-preview | 28 | 10/31/2026 |
| 5.0.30-preview | 182 | 10/31/2026 |
| 5.0.29-preview | 42 | 10/31/2026 |
| 5.0.28-preview | 137 | 10/31/2026 |
| 4.9.24 | 583 | 10/25/2026 |
| 4.8.20 | 246 | 9/23/2026 |
| 4.8.19 | 288 | 9/12/2026 |
| 4.1.1 | 173 | 8/2/2026 |
| 4.1.0 | 215 | 8/1/2026 |
| 4.0.2 | 493 | 5/13/2026 |
| 4.0.1 | 106 | 5/9/2026 |
| 4.0.0 | 1,304 | 4/18/2026 |
| 3.4.0 | 349 | 4/18/2026 |
| 3.2.5 | 279 | 4/18/2026 |
| 3.2.3 | 2,718 | 4/17/2026 |
# Changelog
## 5.0.x
5.0 targets .NET 11 and uses C# 15 in the public API. Read
[Migrating to 5.0](docs/articles/migrating-to-5.md) before upgrading. The 4.x line (.NET 10)
continues on the `support/4.x` branch (published from `release/4.x`).
### Breaking
- **Strong-named** with the CodeLogic key (public key token `53a89305eedc5d98`), shared with CodeLogic.Libs. Assemblies that reference CodeLogic now bind to a strong-named identity.
- **Targets `net11.0` only** (was `net10.0`).
- **`Error` is a `closed record`** with one subtype per kind: `NotFoundError`,
`ValidationError`, `InternalError`, `UnauthorizedError`, `ConflictError`, `TimeoutError`,
`UnavailableError`. The factories are unchanged and now return those subtypes. Errors
compare by value.
- **`Result<T>` is a union of `T | Error`, and `Result` is a union of `Ok | Error`.** A
`switch` over them is checked for exhaustiveness. All 4.x members remain. A
`default(Result<T>)` is empty: it matches the `null` pattern and reports `Error.Empty`.
- **The 13 framework events derive from `closed record FrameworkEvent`.** Subscribing to
`FrameworkEvent` receives all of them.
- Log timestamps, date folders and upper-cased library/plugin IDs use the invariant culture.
This fixes Buddhist-calendar log paths on th-TH and Turkish-I casing.
- **Plugin dependency resolution is isolated.** `PluginLoadContext` no longer hands a plugin any
already-loaded assembly with a matching simple name (which could be another plugin's copy).
`CodeLogic` always resolves to the host; other dependencies resolve from the plugin's own
`.deps.json`/folder first, then from the host. Assemblies whose types cross the host/plugin
boundary must now be listed in the new `PluginOptions.SharedAssemblies` if the plugin ships its own
copy. `PluginLoadContext`'s constructor takes an optional `sharedAssemblies` argument (recompile).
- **Loading a plugin whose ID is already loaded throws** `InvalidOperationException` instead of
silently replacing the running instance. Use `ReloadPluginAsync`.
- **`ResetAsync` disposes the plugin manager registered with `SetPluginManager`** and clears the
registration. Create a new manager after a reset. Using a disposed manager throws
`ObjectDisposedException`.
- `PluginManager.RegisterInProcessPluginAsync` is now truly asynchronous, and `GetAllPlugins` /
`GetLoadedPlugins` return snapshots instead of live views.
- **Localization culture names are validated.** `LocalizationManager` (constructor default culture,
`GenerateTemplatesAsync`, `LoadAsync`, `LoadAllAsync`, `GenerateAllTemplatesAsync`, `ReloadAllAsync`)
accepts only culture names made of ASCII letters, digits, `-` and `_`, and checks that the file path
stays inside the localization directory. Anything else (`../x`, `C:`, spaces, empty) throws
`ArgumentException` instead of reading or writing a file outside the directory.
- **Secret config fields are masked whatever their type.** `GetSchema(..., includeSecrets: false)` now
replaces every set value of a `[ConfigField(Secret = true)]` field with `ConfigField.SecretMask`:
numbers, `bool`, `Guid`, `DateTime`, enums and lists of them, not just strings (they used to be
returned in clear text). `null` and `""` stay visible. Secret enum fields no longer list
`AllowedValues`. `UpdateSectionAsync` restores an echoed mask in such a field from the stored value
(lists by position, list entries paired by key like string secrets); a mask with no stored
counterpart is rejected with a validation error.
- **Library dependency versions use SemVer 2.0.** A `MinVersion` check now compares pre-release
versions instead of skipping them (`5.0.0-preview.1` no longer satisfies `>= 5.0.0`), and a set
`MinVersion` whose own value or the loaded library's `Version` cannot be parsed fails dependency
validation instead of passing silently.
- **`SemanticVersion` follows SemVer 2.0.** It parses `-prerelease` and `+build` suffixes (new
`Prerelease`, `BuildMetadata`, `IsPrerelease` and a constructor overload taking them), orders by
SemVer precedence and ignores build metadata for equality. Leading zeros in numeric components and
signs/whitespace inside components are now rejected, and `ToString()` includes the suffixes.
- `LibraryManager.GetAllLibraries` and `GetLoadedLibraries` return snapshots instead of live views.
### Added
- `Result<T>.Bind`, `Match`, `MapAsync`, `BindAsync`; `Result.Bind`, `Match`; `Ok`;
`Error.Empty`; value equality for `Result` and `Result<T>`.
- `Result.ThrowIfFailure()`, the non-generic counterpart of `ValueOrThrow`.
- `ResultFailedException`, which carries the `Error`. `ValueOrThrow` and `ThrowIfFailure` throw it. It
derives from `InvalidOperationException`, so existing `catch` blocks keep working; checks for the exact
type (`Assert.Throws<InvalidOperationException>`) need `ThrowsAny` or the new type.
- `CodeLogicEnvironment.FrameworkVersion`: the CodeLogic assembly's informational version without
the `+commit` suffix.
### Changed
- Builds with C# 15. Comparer and capacity collections use collection expression arguments
(`[with(StringComparer.OrdinalIgnoreCase), ...]`).
- Async methods compile to .NET 11 runtime-native async (shorter stack traces, lower overhead).
- `object` locks are replaced by `System.Threading.Lock`, and `JsonSerializerOptions`
instances are cached.
- `src` builds with `AnalysisLevel=latest-recommended`.
- `global.json` pins the .NET 11 SDK, and CI installs it from there.
- CI also builds `samples/CodeLogic.Demo.Console` and `samples/CodeLogic.Demo.Web`, so public API
breaks show up in pull requests.
- `--version`, `--info`, the generated `CodeLogic.json`, the first-run marker and the
`FrameworkConfig.Version` default report `CodeLogicEnvironment.FrameworkVersion` instead of the
hardcoded `3.0.0`.
- `PluginManager` unload no longer forces ten blocking full garbage collections while holding
the lifecycle lock. They could never complete the unload (the returned `LoadedPlugin` still
references the load context) and only stalled the process for 100 ms or more per plugin. The context
is collected once nothing references the plugin's types.
### Fixed
- **Plugin lifecycle is serialized.** Load, register, unload, reload, dispose and hot reload run one at
a time; readers and health checks work on snapshots, so hot reload can no longer corrupt the plugin
table or throw "collection was modified". Calling lifecycle methods on the same manager from a plugin's
lifecycle callback or an `OnPluginError` handler of the operation in progress throws
`InvalidOperationException` instead of deadlocking.
- **Plugin failures clean up.** A failed load disposes the instance (after `OnUnloadAsync` when
`OnStartAsync` was entered) and unloads its load context. A throwing `OnUnloadAsync` no longer skips
`Dispose`, context unload and removal. Exceptions from `OnPluginLoaded`/`OnPluginUnloaded` handlers
are no longer reported as plugin failures.
- **Default config generation is serialized with saves and loads**, and atomic writes use a unique
temp file, so concurrent generate/load/save can no longer collide on `config.json.tmp` or overwrite
a competing save with defaults.
- **Log directories recover.** A failed directory creation is no longer cached, and a log folder
deleted while the process runs is recreated on the next line.
- **Log paths compare case-sensitively on Linux** (case-insensitively on Windows and macOS), so
`App.log` and `app.log` no longer share one writer there.
- **Idle log files are closed even when nothing else is logged**; previously the idle check only ran
after new entries arrived.
- **Secret masking covers defaults and string lists.** `GetSchema` masks the default value of secret
string fields and every entry of secret `List<string>` fields. Echoed masks in such lists are
restored by position; an update that changes the list length while entries are still masked is
rejected instead of saving the mask.
- `release.yml` checks out the commit that triggered the run (`github.sha`) instead of the branch
head, so the packed commit is always the verified, tagged one.
- **Plugin reentrancy detection is per manager.** The flow marker was a static shared by every
`PluginManager`: when a callback of manager A called manager B, and a callback of B called back into
A, A's check missed it and the call deadlocked on A's lifecycle lock. It now throws
`InvalidOperationException` like any other reentrant call; independent managers can still nest calls.
- **`LibraryManager` readers are safe during registration.** The library list is a copy-on-write
snapshot, so the assembly resolver, health checks, config watcher and accessors no longer throw
"Collection was modified" (or read a torn list) while a library is being registered.
- **`LocalizationManager.ReloadAllAsync` no longer empties the cache first.** It loads into a new
snapshot and swaps it in, so concurrent `Get<T>` calls keep working during a reload and after a
failed one. Loads are serialized and `Get<T>` reads an immutable snapshot.
- **`CodeLogicRuntime.ResetAsync` clears all run state.** It unregisters the CTRL+C / process-exit
handlers (re-initializing registers them again as configured), drops every event bus subscription
made during the previous run, and detaches the framework logger.
### Documentation
- Plugin docs and README examples use the real API (`LoadPluginAsync`/`UnloadPluginAsync`/
`ReloadPluginAsync`, the actual `PluginManager` constructor, `PluginState` values and manifest
members), and the isolation section describes what the load context does and does not isolate.
- The Core, Framework and Reference guides and the in-depth getting-started page are now part of the
docs site.
- The web demo's config endpoints carry authentication/authorization guidance.
### Performance
Measured with `benchmarks/CodeLogic.Benchmarks` against 5.0.28-preview.
- **Log calls no longer touch the disk.** Lines go to one background writer per process, which keeps
files open, writes them in order and flushes when the queue is empty. An `Info` line costs about
0.6 µs instead of about 130 µs. `StopAsync`, process exit and an unhandled exception flush pending
lines; `Logger.FlushAsync()` is available to hosts.
- **Disabled log levels are almost free.** New interpolated-string overloads skip evaluating the holes
when the level is off (8 ns and 0 B instead of 460 ns and 1 KB). New `ILogger.IsEnabled(LogLevel)`.
The format overloads take `params ReadOnlySpan<object?>` and don't format when the level is off.
- **`EventBus.Publish` takes no lock and allocates nothing.** Subscribers live in a copy-on-write
snapshot; a publish sees the subscribers that existed when it started. 8 ns instead of 29 ns for
one subscriber.
## 4.9.x
### Added
- **Live config changes for libraries.** A library can implement the new optional
`IConfigReloadable` interface to apply a config change without a restart. It receives the
old and new section values and applies only what changed, e.g. reconnecting one database
out of three while the others stay connected. Called after `Libraries.UpdateConfigAsync`,
`Libraries.ResetConfigAsync`, the new `Libraries.ReloadConfigAsync`, and the file watcher.
- `ConfigDiff`: `Keyed(old, new)` splits keyed instance sections into added / removed /
changed / unchanged; `ChangedProperties` and `RequiresRestart` report changed fields.
- `ConfigChangedEventArgs` now carries `ConfigType`, `OldValue` / `NewValue` and
`GetOld<T>()` / `GetNew<T>()`.
- Opt-in config file watcher: `libraries.configHotReload { enabled, debounceMs }` in
`CodeLogic.json` reloads a library section when its `config*.json` file is edited. Invalid
edits are logged and ignored; the framework's own writes don't re-trigger it.
- `ConfigChangedEvent` (every committed change) and `ConfigApplyFailedEvent` (a saved change a
library could not apply). `ConfigReloadedEvent` is now actually published.
- `LoadedLibrary.ConfigApplyError`; a library that failed to apply a change reports Degraded
health until a later change applies.
- `ConfigField.IsOverridden` in `GetSchema` marks fields set by runtime overrides.
### Changed
- Package author and copyright are now Zyntal.com. NuGet release notes come from this
changelog, and the NuGet readme no longer shows the oversized logo.
- `LibraryManager` / `Libraries` `UpdateConfigAsync` and `ResetConfigAsync` return
`ConfigApplyResult` (validation result plus apply error) instead of `ConfigValidationResult`.
`IsValid` and `Errors` are still there; `Applied` is new. Recompile callers.
- Config files are written atomically (temp file + rename), and changes to config sections
are serialized.
### Fixed
- Runtime config overrides (`OverrideConfig`) were lost on reload/update and were written to
the config file when the section was saved. They are now re-applied after every change and
never persisted.
- `UpdateSectionAsync` / `UpdateConfigAsync` and `GetSchema` threw `TargetParameterCountException`
for any section with a `List<T>` of objects (they reflected over the list's indexer). List
entries now appear in the schema as `{list}.{index}.{field}` with secrets masked, and masked
secrets sent back are restored from the matching previous entry (matched by its `Id` / `Name` /
`Key` / `…Id` property, else by position when the length is unchanged).
## 4.8.x
### Changed
- Unified the version line with `CodeLogic.Libs` on **4.8.x**. The framework and every
official library now share one `major.minor`, so a given `4.8.<patch>` means the same
generation across all packages.
- Moved `major.minor` into a hand-controlled repo-root `version.txt`. The patch component
is the CI run number, composed at pack time — no version is written back to the
repository during a release.
- `AssemblyVersion` and `FileVersion` are now derived as `Major.Minor.0.0` (`4.8.0.0`)
instead of being hardcoded. Assembly binding stays fixed across the whole `4.8` line,
so every patch loads interchangeably without a binding redirect.
### Removed
- Removed commit-message-driven version bumping from the release workflow. `feat:` and
`!`/`BREAKING CHANGE` prefixes no longer move `major` or `minor`; the version line moves
only when `version.txt` is edited.
### Added
- Added a `prerelease` branch trigger to the release workflow, matching `CodeLogic.Libs`.
Pushing to `prerelease` publishes `4.8.<run>-preview+<sha>`; pushing to `release`
publishes `4.8.<run>+<sha>`.
- Added [Reference/versioning.md](docs/Reference/versioning.md) documenting the scheme,
the run-number patch, assembly binding identity, and how to move to the next minor.
### Notes
- The previous published line was `4.1.x` with `AssemblyVersion` pinned at `3.3.0.0`.
Assembly identity now moves to `4.8.0.0`, which is a binding-breaking change for
anything compiled against the old value; recompilation is required.