diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4f91370..c3f1995 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -37,11 +37,11 @@ jobs:
dotnet tool restore
- name: Build (Release)
- run: dotnet build PatternKit.sln --configuration Release --no-restore /p:ContinuousIntegrationBuild=true
+ run: dotnet build PatternKit.slnx --configuration Release --no-restore /p:ContinuousIntegrationBuild=true
- name: Test with coverage
run: |
- dotnet test PatternKit.sln \
+ dotnet test PatternKit.slnx \
--configuration Release \
--collect:"XPlat Code Coverage" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura \
@@ -139,7 +139,7 @@ jobs:
- name: Build (Release)
run: >
- dotnet build PatternKit.sln
+ dotnet build PatternKit.slnx
--configuration Release
--no-restore
/p:ContinuousIntegrationBuild=true
@@ -150,7 +150,7 @@ jobs:
- name: Test with coverage (Release)
run: |
- dotnet test PatternKit.sln \
+ dotnet test PatternKit.slnx \
--configuration Release \
--collect:"XPlat Code Coverage" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura \
@@ -160,7 +160,7 @@ jobs:
- name: Pack (all packable projects)
run: >
- dotnet pack PatternKit.sln
+ dotnet pack PatternKit.slnx
--configuration Release
--no-build
--output ./artifacts
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 3bc0eff..bf595c0 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -36,10 +36,10 @@ jobs:
languages: csharp
- name: Restore
- run: dotnet restore PatternKit.sln --use-lock-file
+ run: dotnet restore PatternKit.slnx --use-lock-file
- name: Build
- run: dotnet build PatternKit.sln --configuration Release --no-restore
+ run: dotnet build PatternKit.slnx --configuration Release --no-restore
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
diff --git a/.gitignore b/.gitignore
index 9f2efde..57e8bba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,5 @@ riderModule.iml
.idea/
_site/
api/
-*.user
\ No newline at end of file
+*.user
+TestResults/
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 7d5ba3d..bae549a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,12 +1,51 @@
- net9.0
+ netstandard2.0;netstandard2.1;net8.0;net9.0
enable
enable
- latest
+ preview
+ true
true
true
true
$(NoWarn);1591
+
+
+ Jerrett Davis and contributors
+ JDH Productions LLC.
+ PatternKit
+ PatternKit is a collection of design patterns implemented in a fluent API style for .NET, enabling developers to easily integrate common design patterns into their applications with readable and maintainable code.
+ design-patterns;fluent;fluent-api;dotnet;
+
+ MIT
+
+ git
+ https://github.com/JerrettDavis/PatternKit
+ https://github.com/JerrettDavis/PatternKit
+
+ README.md
+ tiny.png
+
+ true
+ snupkg
+ true
+ true
+
+ true
+
+
+
+ false
+ false
+ true
+
+
+
+
+
+
+
diff --git a/PatternKit.Core/PatternKit.Core.csproj b/PatternKit.Core/PatternKit.Core.csproj
deleted file mode 100644
index 2accc01..0000000
--- a/PatternKit.Core/PatternKit.Core.csproj
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
- net9.0
- latest
- enable
- enable
- PatternKit
-
-
-
diff --git a/PatternKit.sln b/PatternKit.sln
deleted file mode 100644
index 55e55ef..0000000
--- a/PatternKit.sln
+++ /dev/null
@@ -1,35 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatternKit.Core", "PatternKit.Core\PatternKit.Core.csproj", "{467854B0-1662-4C93-A79C-2AFE61D5CB5E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatternKit.Examples", "src\PatternKit.Examples\PatternKit.Examples.csproj", "{80469DAB-D194-4093-A13F-314B39B01DBC}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatternKit.Tests", "test\PatternKit.Tests\PatternKit.Tests.csproj", "{FAAB375A-8E06-4943-BF8B-136CB5D11A9B}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6C57A958-E01A-4D6F-BA17-0F95CCAC312E}"
- ProjectSection(SolutionItems) = preProject
- .gitignore = .gitignore
- README.md = README.md
- Directory.Build.props = Directory.Build.props
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {467854B0-1662-4C93-A79C-2AFE61D5CB5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {467854B0-1662-4C93-A79C-2AFE61D5CB5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {467854B0-1662-4C93-A79C-2AFE61D5CB5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {467854B0-1662-4C93-A79C-2AFE61D5CB5E}.Release|Any CPU.Build.0 = Release|Any CPU
- {80469DAB-D194-4093-A13F-314B39B01DBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {80469DAB-D194-4093-A13F-314B39B01DBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {80469DAB-D194-4093-A13F-314B39B01DBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {80469DAB-D194-4093-A13F-314B39B01DBC}.Release|Any CPU.Build.0 = Release|Any CPU
- {FAAB375A-8E06-4943-BF8B-136CB5D11A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FAAB375A-8E06-4943-BF8B-136CB5D11A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FAAB375A-8E06-4943-BF8B-136CB5D11A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FAAB375A-8E06-4943-BF8B-136CB5D11A9B}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
-EndGlobal
diff --git a/PatternKit.slnx b/PatternKit.slnx
new file mode 100644
index 0000000..d866dc0
--- /dev/null
+++ b/PatternKit.slnx
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 00ff445..ea60f3e 100644
--- a/README.md
+++ b/README.md
@@ -64,11 +64,11 @@ if (parse.Execute("123", out var n))
PatternKit will grow to cover **Creational**, **Structural**, and **Behavioral** patterns with fluent, discoverable APIs:
-| Category | Patterns ✓ = implemented |
-| -------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| **Creational** | Factory (planned) • Builder (planned) • Prototype (planned) • Singleton (planned) |
-| **Structural** | Adapter (planned) • Bridge (planned) • Composite (planned) • Decorator (planned) • Facade (planned) • Flyweight (planned) • Proxy (planned) |
-| **Behavioral** | Strategy ✓ • TryStrategy ✓ • Chain of Responsibility (planned) • Command (planned) • Iterator (planned) • Mediator (planned) • Memento (planned) • Observer (planned) • State (planned) • Template Method (planned) • Visitor (planned) |
+| Category | Patterns ✓ = implemented |
+| -------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Creational** | Factory (planned) • Builder (planned) • Prototype (planned) • Singleton (planned) |
+| **Structural** | Adapter (planned) • Bridge (planned) • Composite (planned) • Decorator (planned) • Facade (planned) • Flyweight (planned) • Proxy (planned) |
+| **Behavioral** | Strategy ✓ • TryStrategy ✓ • ActionStrategy ✓ • Chain of Responsibility (planned) • Command (planned) • Iterator (planned) • Mediator (planned) • Memento (planned) • Observer (planned) • State (planned) • Template Method (planned) • Visitor (planned) |
Each pattern will ship with:
@@ -163,3 +163,4 @@ PatternKit is inspired by:
* Fluent APIs from **ASP.NET Core**, **System.Linq**, and modern libraries
* The desire to make patterns **readable**, **performant**, and **fun** to use in 2025+
+
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..1e3d01f
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,3 @@
+ignore:
+ - "**/*.Tests/**"
+ - "**/*Tests*/**"
diff --git a/docs/examples/auth-logging-chain.md b/docs/examples/auth-logging-chain.md
new file mode 100644
index 0000000..ab3cc4b
--- /dev/null
+++ b/docs/examples/auth-logging-chain.md
@@ -0,0 +1,162 @@
+# Auth & Logging with `ActionChain`
+
+A tiny example that shows how to use **PatternKit.Behavioral.Chain** to build request-logging and auth checks with **no `if`/`else`** and **first-match-wins** semantics.
+
+* **Goal:** log a request id when present, short-circuit unauthorized `/admin/*` requests, and (otherwise) log the method/path.
+* **Key idea:** branchless pipelines using `When(...).ThenContinue(...)`, `When(...).ThenStop(...)`, and a `Finally(...)` tail.
+
+---
+
+## The demo pipeline
+
+```csharp
+using PatternKit.Behavioral.Chain;
+
+public readonly record struct HttpRequest(string Method, string Path, IReadOnlyDictionary Headers);
+public readonly record struct HttpResponse(int Status, string Body);
+
+public static class AuthLoggingDemo
+{
+ public static List Run()
+ {
+ var log = new List();
+
+ var chain = ActionChain.Create()
+ // 1) request id (continue)
+ .When(static (in r) => r.Headers.ContainsKey("X-Request-Id"))
+ .ThenContinue(r => log.Add($"reqid={r.Headers["X-Request-Id"]}"))
+
+ // 2) admin requires auth (stop)
+ .When(static (in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal)
+ && !r.Headers.ContainsKey("Authorization"))
+ .ThenStop(_ => log.Add("deny: missing auth"))
+
+ // 3) tail – runs only if the chain did not stop
+ .Finally((in r, next) =>
+ {
+ log.Add($"{r.Method} {r.Path}");
+ next(r); // terminal next is a no-op
+ })
+ .Build();
+
+ // simulate
+ chain.Execute(new HttpRequest("GET", "/health", new Dictionary()));
+ chain.Execute(new HttpRequest("GET", "/admin/metrics", new Dictionary()));
+
+ return log;
+ }
+}
+```
+
+### What it logs (strict-stop semantics)
+
+`Run()` returns:
+
+```
+GET /health
+deny: missing auth
+```
+
+Why not a third line (`GET /admin/metrics`)? Because **`.ThenStop(...)` halts the pipeline** and the `Finally(...)` tail does **not** run after a stop. That’s by design—great for auth short-circuits.
+
+---
+
+## Mental model
+
+* **First match wins**: the first `When(...)` whose predicate is `true` executes its `Then...` and the others are skipped.
+* **`.ThenContinue(...)`**: perform side effects and continue evaluating later steps (and eventually `Finally`).
+* **`.ThenStop(...)`**: perform side effects and **end the pipeline immediately** (no `Finally`).
+* **`Finally(...)`**: tail step that runs **only if the chain didn’t stop**.
+
+---
+
+## Variant: “Always log method/path”
+
+If you want method/path to be logged **even when denied**, move that logging up front with `Use(...)`:
+
+```csharp
+var chain = ActionChain.Create()
+ .Use((in r, next) => { log.Add($"{r.Method} {r.Path}"); next(r); })
+ .When(static (in r) => r.Headers.ContainsKey("X-Request-Id"))
+ .ThenContinue(r => log.Add($"reqid={r.Headers["X-Request-Id"]}"))
+ .When(static (in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal)
+ && !r.Headers.ContainsKey("Authorization"))
+ .ThenStop(_ => log.Add("deny: missing auth"))
+ .Build();
+```
+
+Now the simulated run yields:
+
+```
+GET /health
+GET /admin/metrics
+deny: missing auth
+```
+
+(Logging happens before the deny short-circuit.)
+
+---
+
+## TinyBDD smoke tests
+
+Here’s a compact set that locks in the strict-stop behavior (no method/path after deny):
+
+```csharp
+using TinyBDD;
+using TinyBDD.Xunit;
+using Xunit;
+using Xunit.Abstractions;
+
+[Feature("Auth & Logging demo (ActionChain)")]
+public sealed class AuthLoggingDemoTests(ITestOutputHelper output) : TinyBddXunitBase(output)
+{
+ [Scenario("Run() logs health, then denies admin without trailing method/path")]
+ [Fact]
+ public async Task Demo_Run_Smoke()
+ {
+ await Given("the demo Run() helper", () => (Func>)AuthLoggingDemo.Run)
+ .When("running it", run => run())
+ .Then("first line is GET /health", log => log.ElementAtOrDefault(0) == "GET /health")
+ .And("second line is deny", log => log.ElementAtOrDefault(1) == "deny: missing auth")
+ .And("stops after deny (no third line)", log => log.Count == 2)
+ .AssertPassed();
+ }
+
+ [Scenario("Order with both: X-Request-Id then deny (no method/path)")]
+ [Fact]
+ public async Task RequestId_Then_Deny()
+ {
+ var log = new List();
+ var chain = ActionChain.Create()
+ .When(static (in r) => r.Headers.ContainsKey("X-Request-Id"))
+ .ThenContinue(r => log.Add($"reqid={r.Headers["X-Request-Id"]}"))
+ .When(static (in r) => r.Path.StartsWith("/admin") && !r.Headers.ContainsKey("Authorization"))
+ .ThenStop(_ => log.Add("deny: missing auth"))
+ .Finally((in r, next) => { log.Add($"{r.Method} {r.Path}"); next(r); })
+ .Build();
+
+ await Given("the chain and log", () => (chain, log))
+ .When("GET /admin/x with X-Request-Id and no auth", t =>
+ {
+ var (c, l) = t;
+ c.Execute(new HttpRequest("GET", "/admin/x", new Dictionary{{"X-Request-Id","rid-7"}}));
+ return l;
+ })
+ .Then("reqid first", l => l.ElementAtOrDefault(0) == "reqid=rid-7")
+ .And("deny second", l => l.ElementAtOrDefault(1) == "deny: missing auth")
+ .And("no method/path after stop", l => l.Count == 2)
+ .AssertPassed();
+ }
+}
+```
+
+> Tip: use `ElementAtOrDefault` in tests to avoid index exceptions and get clearer failure messages.
+
+---
+
+## When to use this pattern
+
+* **Middleware-like** concerns where some steps are pure side effects (logging, metrics, request-id) and others **short-circuit** (auth, feature gates).
+* Places where you want **declarative ordering** and **no nested conditionals**—add/remove rules without touching the rest.
+
+That’s it—simple, predictable, and production-friendly.
diff --git a/docs/examples/coercer.md b/docs/examples/coercer.md
new file mode 100644
index 0000000..ef06eca
--- /dev/null
+++ b/docs/examples/coercer.md
@@ -0,0 +1,145 @@
+# Coercer\ — strategy-driven, allocation-light type coercion
+
+**Goal:** turn “whatever came in” (JSON, strings, primitives) into the *type you actually want*—without `if/else` piles or reflection in the hot path.
+
+This demo shows how `Coercer` compiles a tiny **TryStrategy** pipeline once per closed generic (e.g. `Coercer`, `Coercer`) and then uses **first-match-wins** handlers to coerce values at runtime.
+
+---
+
+## Why use it
+
+* **Fast path first:** already-typed values return immediately (no copies, no boxing/unboxing churn).
+* **Deterministic order:** a small array of non-capturing delegates runs top-to-bottom; the first success wins.
+* **Culture-safe:** the fallback conversion uses `InvariantCulture` so your tests and prod behave the same on Windows/Linux.
+* **Tiny surface:** just call `Coercer.From(object?)` or `any.Coerce()`.
+
+---
+
+## Quick start
+
+```csharp
+using System.Text.Json;
+using PatternKit.Examples.Coercion;
+
+// From JSON
+var i = Coercer.From(JsonDocument.Parse("123").RootElement); // 123
+var b = Coercer.From(JsonDocument.Parse("true").RootElement); // true
+var s = Coercer.From(JsonDocument.Parse("\"hello\"").RootElement); // "hello"
+var xs = Coercer.From(JsonDocument.Parse("[\"a\",\"b\"]").RootElement); // ["a","b"]
+
+// From “anything”
+int? viaExt = ((object)"27").Coerce(); // 27 (Convertible fallback, invariant)
+double? d = ((object)"2.25").Coerce(); // 2.25
+string[]? one = ((object)"only").Coerce(); // ["only"]
+
+// Nulls return default(T)
+int? none = Coercer.From(null); // null
+```
+
+---
+
+## What `Coercer` handles by default
+
+`Coercer.From(object?)` applies these steps **in order**:
+
+1. **Direct cast (fast path)**
+ If the input is already `T`, return it as-is.
+
+2. **Typed handlers** (first-match-wins)
+
+| Input | Target `T` | Behavior |
+| -------------------------- | ------------------------------------------- | -------------------------------------------- |
+| `JsonElement` (any) | `string` | `je.ToString()` |
+| `JsonElement` (array) | `string[]` | Enumerate elements, `.ToString()` each |
+| `JsonElement` (number) | `int` / `float` / `double` (incl. nullable) | `GetInt32()` / `GetSingle()` / `GetDouble()` |
+| `JsonElement` (true/false) | `bool` (incl. nullable) | `GetBoolean()` |
+| `string` | `string[]` | Wrap into single-element array |
+
+3. **Convertible fallback (last resort)**
+ If the input is `IConvertible` and the target (or its nullable underlying type) is **primitive or `decimal`**, use:
+
+```csharp
+Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture)
+```
+
+If no handler succeeds, return `default(T)`.
+
+---
+
+## Ordering & priority (why the results are stable)
+
+The strategy array is compiled once per `Coercer` at type init. Runtime calls **don’t branch on type**; they just loop through a small array of delegates:
+
+* **DirectCast** runs first (zero cost success).
+* A **type-specific block** (e.g., “if T is `string[]`”) injects only the relevant handlers.
+* **ConvertibleFallback** is *always last* so it can’t steal cases that have precise JSON readers.
+
+This order is what makes things like `JsonElement 123 → int` culture-proof and fast.
+
+---
+
+## Culture & precision notes
+
+* The fallback uses **`InvariantCulture`**. `"2.25"` parses as 2.25 regardless of OS locale.
+* JSON numeric handlers (`GetInt32`, `GetSingle`, `GetDouble`) avoid string round-trips and honor JSON number semantics.
+* If you need bankers’ rounding or custom numeric policy, add a bespoke handler (see below).
+
+---
+
+## Extending or customizing
+
+`Coercer`’s default pipeline lives in `Build()`. To add a new target (e.g., `Guid`) or tweak ordering:
+
+* **Library edit**: add a new handler in `Build()` (e.g., for `Guid`), placing it *before* `ConvertibleFallback`.
+* **Wrapper approach**: write your own converter and call it **before** `Coercer`:
+
+```csharp
+static Guid? TryGuid(object? v)
+{
+ if (v is string s && Guid.TryParse(s, out var g)) return g;
+ if (v is JsonElement je && je.ValueKind == JsonValueKind.String &&
+ Guid.TryParse(je.GetString(), out g)) return g;
+ return null;
+}
+
+static Guid? CoerceGuid(object? v) => TryGuid(v) ?? Coercer.From(v);
+```
+
+Use the wrapper when you can’t or don’t want to change the shared coercer.
+
+---
+
+## Error behavior
+
+No exceptions are thrown for failed coercions—handlers just return `false` and the next one tries. The fallback catches and swallows `Convert.ChangeType` exceptions.
+
+---
+
+## Tests (TinyBDD)
+
+See `PatternKit.Examples.Tests/Coercion/CoercerTests.cs`. They read like specifications:
+
+* JSON → primitives (`int`, `float`, `double`, `bool`)
+* JSON → `string` / `string[]`
+* Single `string` → `string[]`
+* Convertible fallback for `"27"`, `"2.25"`, etc.
+* Ordering guard: JSON numeric handlers beat the fallback
+* Culture stability (`InvariantCulture`)
+
+> Tip (Linux/macOS): our test host pins `en-US` to keep currency/number formatting stable across platforms.
+
+---
+
+## Performance cheatsheet
+
+* **No LINQ** in the hot path; the handlers are a flat array of non-capturing delegates.
+* **Zero alloc** for direct-cast successes; obvious allocations only when building `string[]` or when JSON `.ToString()` is used.
+* **Thread-safe**: the strategy array is immutable per closed generic and reused across calls.
+
+---
+
+## Troubleshooting
+
+* **Got `default(T)` back?** No handler matched. Check target type and the exact input shape (`JsonElement.ValueKind`, nullable vs. non-nullable).
+* **Parsed a localized number incorrectly?** Ensure you rely on JSON handlers (preferred) or feed `InvariantCulture`-formatted text; the fallback already uses `InvariantCulture`.
+* **Need decimals?** Add a `JsonElement → decimal` handler, then slot it before `ConvertibleFallback`.
diff --git a/docs/examples/composed-notification-strategy.md b/docs/examples/composed-notification-strategy.md
new file mode 100644
index 0000000..d48acc3
--- /dev/null
+++ b/docs/examples/composed-notification-strategy.md
@@ -0,0 +1,249 @@
+# Composed, Preference-Aware Notification Strategy (Email/SMS/Push/IM)
+
+> **TL;DR**
+> We compose a notification strategy that:
+>
+> 1. prepends **SMS** for critical messages,
+> 2. otherwise uses the user’s **preferred channel order** (de-duped, first occurrence wins),
+> 3. evaluates **per-channel gates** (identity/presence/rate),
+> 4. executes the **first viable** channel, and
+> 5. **falls back to Email** (by design) if nothing else is viable.
+
+This example demonstrates how to build a production-friendly dispatcher with `` and a small set of explicit, testable components. It also shows how we validate behavior using **TinyBDD** scenarios with xUnit.
+
+---
+
+## Why a composed strategy (and not a giant `switch`)?
+
+Branching logic for multi-channel notifications tends to sprawl:
+
+* “If critical, always try SMS first…”
+* “…but only if opted in + not rate-limited.”
+* “Otherwise, follow user preferences…”
+* “…making sure IM is online and Push isn’t DND.”
+* “…and if none apply, still send *something*.”
+
+A composed strategy lets us encode these as **named predicates and handlers**, then **assemble** them declaratively. The result is readable, testable, and easy to extend.
+
+---
+
+## Model types
+
+* **Channels**: `` — `Email`, `Sms`, `Push`, `Im`.
+* **Input**: `` — `(UserId, Message, IsCritical, Locale?)`.
+* **Output**: `` — `(Channel, Success, Info?)`.
+
+Dependencies (`IIdentityService`, `IPresenceService`, `IRateLimiter`, `IPreferenceService`, and four sender interfaces) are small and focused. They can be synchronous or asynchronous (we use `ValueTask`/`ValueTask` everywhere).
+
+---
+
+## The core building block: `AsyncStrategy`
+
+We use `` to build branchy flows that look like:
+
+```csharp
+var strategy = AsyncStrategy.Create()
+ .When(IsCritical).Then(ExecuteCritical)
+ .Default(ExecuteByPrefs)
+ .Build();
+```
+
+* **When/Then** pairs add branches in order.
+* **Default** is the fallback handler if no predicate matches.
+* Each predicate/handler has async and sync adapters, so you can pass method groups without lambda allocations.
+
+---
+
+## Channel policies = Gate + Send
+
+We keep channels self-contained with ``:
+
+* `Gate : AsyncStrategy` — **all** checks must pass.
+* `Send : Handler` — the sender to invoke if the gate allows it.
+
+`` wires this up:
+
+* **Push** gate: `HasPushToken && !DoNotDisturb && RateOkPush`
+* **IM** gate: `OnlineIm && RateOkIm`
+* **Email** gate: `HasVerifiedEmail && RateOkEmail`
+* **SMS** gate: `HasSmsOptIn && RateOkSms`
+
+> Gates short-circuit on first failure (sequential checks). They’re built once and reused.
+
+**Why sequential?** It preserves intent and makes short-circuit behavior observable in tests (e.g., “no token → don’t even check DND or rate”).
+
+---
+
+## Preference-aware composition
+
+`` builds the top-level strategy:
+
+1. **Critical path**
+ If `` is true, we **prepend `Sms`** to the order (if it isn’t already first) and evaluate gates in that order.
+
+2. **Preferred path**
+ Otherwise we ask `` for the user’s order, **de-dupe while preserving first occurrence**, then build a per-request strategy that tries each policy’s gate in that order.
+
+3. **Fallback**
+ If nothing matches, we call **Email’s send handler** as a final attempt **even if Email’s gate would fail**. That is by design to guarantee a last-ditch delivery.
+
+A trimmed version of the ordered execution:
+
+```csharp
+var distinct = order.Distinct().ToList(); // preserve first occurrence
+
+var builder = AsyncStrategy.Create();
+
+builder = distinct
+ .Select(ch => policies[ch])
+ .Aggregate(builder, (b, p) => b.When(p.Gate.ExecuteAsync).Then(p.Send));
+
+var strat = builder.Default(policies[Channel.Email].Send).Build();
+return await strat.ExecuteAsync(ctx, ct);
+```
+
+---
+
+## Gates cheat-sheet
+
+| Channel | Checks (all must pass) |
+| --------- | --------------------------------------------- |
+| **Push** | `HasPushToken`, `!DoNotDisturb`, `RateOkPush` |
+| **IM** | `OnlineIm`, `RateOkIm` |
+| **Email** | `HasVerifiedEmail`, `RateOkEmail` |
+| **SMS** | `HasSmsOptIn`, `RateOkSms` |
+
+> We invert DND using a tiny zero-alloc combinator:
+>
+> ```csharp
+> internal static ValueTask Continue(this ValueTask t, Func f) =>
+> t.IsCompletedSuccessfully ? new(f(t.Result)) : Awaited(t, f);
+> ```
+
+---
+
+## Extending with a new channel
+
+1. Add to `enum Channel`.
+2. Implement sender interface.
+3. Add identity/presence/rate checks (if any).
+4. Add a gate via `ChannelPolicyFactory.Gate([...])`.
+5. Add `[newChannel] = new(gate, SendNewChannel)` to `CreateAll()`.
+6. Update tests (see below).
+
+---
+
+## Testing with TinyBDD
+
+We use **TinyBDD + xUnit** to express readable, executable scenarios. Each test builds a small **harness** of fakes/spies and asserts outcomes.
+
+### Main scenarios
+
+1. **Preference order: first viable → Push**
+
+```csharp
+await Given("Push is first & all push guards pass", () =>
+ CreateHarness(h => {
+ h.Prefs.Set([Channel.Push, Channel.Im, Channel.Email]);
+ h.Id.PushToken = true;
+ h.Presence.DoNotDisturb = false;
+ h.Rate.Set(Channel.Push, true);
+ }))
+ .When("executing the strategy", Run)
+ .Then("result channel should be Push", x => x.R.Channel == Channel.Push)
+ .And("push called exactly once", x => x.H.Push.Calls == 1)
+ .And("no other senders called", x => x.H.Im.Calls == 0 && x.H.Email.Calls == 0 && x.H.Sms.Calls == 0)
+ .AssertPassed();
+```
+
+2. **Skip non-viable Push → IM**, **Critical → SMS first**, **Empty prefs → Email**,
+ **De-dupe preserves first occurrence** and **evaluates each gate once**,
+ **Email gate respected when first in order → next viable (SMS)**,
+ **IM requires Online + Rate**, **Rate limiter is per-channel**,
+ **Default Email fallback ignores Email gate**.
+
+### Extended scenarios (behavioral edges)
+
+1. **Push short-circuits when no token**
+ We prove that **DND and rate aren’t touched** when the first check fails:
+
+```csharp
+Then("push token checked once", x => x.t.id.HasPushTokenCalls == 1)
+And("DND not checked", x => x.t.presence.DndCalls == 0)
+And("push rate not checked", x => x.t.rate.PushCalls == 0)
+```
+
+2. **IM sender failure does not fall through**
+ If IM is chosen and the sender returns `Success=false`, we **do not** try Email afterward. The result is IM/false.
+
+3. **Fallback Email throws → cancellation propagates**
+ If preferences force fallback to Email and the Email sender throws (e.g., a cancelled token), we surface the exception (`TaskCanceledException` in the sample).
+
+4. **Tie-breakers follow declared order**
+ When **all gates pass**, selection follows the declared preference order (e.g., `[Sms, Push, Email, Im]` picks `Sms`).
+
+---
+
+## Test harness fakes & spies
+
+We use minimal test doubles:
+
+* **Fakes** (`Fake*`) to collect call counts and capture `SendContext`.
+* **Spies** (`Spy*`) to verify **short-circuiting** (e.g., how many times `HasPushTokenAsync` was called).
+* **Throwing/Failing senders** to exercise cancellation and “no fall-through” behavior.
+
+This keeps assertions crisp and maps 1:1 to the production code’s intent.
+
+---
+
+## Performance and reliability notes
+
+* **`ValueTask` everywhere**: avoid allocations on sync-fast paths.
+* **Method groups over lambdas**: fewer allocations, clearer intent.
+* **No per-call closures** inside the strategy builder: policies are created once and reused.
+* **Short-circuit gates**: only the minimum number of checks is executed.
+* **Thread-safe composition**: the built strategy is immutable; ensure your dependencies are thread-safe.
+
+---
+
+## Troubleshooting
+
+* **“Argument 2: cannot convert from ‘method group’…”**
+ Ensure the method group **matches the delegate** exactly. For example, `When(policy.Gate.ExecuteAsync)` expects `Func>`. If your method has default parameters or a different signature, **wrap** it:
+
+ ```csharp
+ b.When((ctx, ct) => policy.Gate.ExecuteAsync(ctx, ct));
+ ```
+
+* **Email fallback ignores Email gate**
+ That’s **intentional**: we guarantee a last-ditch attempt to send something.
+
+---
+
+## Quick start
+
+```csharp
+// Wire your real services here
+var strategy = ComposedStrategies.BuildPreferenceAware(
+ id, presence, rate, prefs, email, sms, push, im);
+
+var result = await strategy.ExecuteAsync(
+ new SendContext(userId, "Hello from PatternKit!", isCritical: false),
+ CancellationToken.None);
+
+// result.Channel: which channel was executed
+// result.Success: whether the sender reported success
+```
+
+---
+
+## Cross-references
+
+* ``
+* ``
+* ``
+* ``
+* ``
+* ``
+* ``
+
diff --git a/docs/examples/config-driven-transaction-pipeline.md b/docs/examples/config-driven-transaction-pipeline.md
new file mode 100644
index 0000000..c231a83
--- /dev/null
+++ b/docs/examples/config-driven-transaction-pipeline.md
@@ -0,0 +1,267 @@
+# Config-driven transaction pipeline (DI + fluent chains)
+
+> **Goal**
+> Build a checkout pipeline where **what runs** and **in what order** comes from configuration, while the execution remains allocation-lean and testable.
+
+This demo layers a small configuration model over the same primitives used in the mediated pipeline:
+
+* **Action chains** for branchless *discounts → tax* and *rounding*
+* **Tender handling** via a first-match router (see the mediated pipeline doc)
+* **DI registration** that composes a single immutable [xref\:PatternKit.Examples.Chain.TransactionPipeline](xref:PatternKit.Examples.Chain.TransactionPipeline) at startup
+
+---
+
+## Quick start
+
+1. **Add configuration** (order matters):
+
+```json
+// appsettings.json
+{
+ "Payment": {
+ "Pipeline": {
+ "DiscountRules": [ "discount:cash-2pc", "discount:loyalty-5pc", "discount:bundle-1off" ],
+ "Rounding": [ "round:charity", "round:nickel-cash-only" ],
+ "TenderOrder": [ "tender:cash", "tender:card" ] // informational
+ }
+ }
+}
+```
+
+2. **Register the pipeline** in DI:
+
+```csharp
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using PatternKit.Examples.Chain.ConfigDriven;
+
+var services = new ServiceCollection();
+services.AddPaymentPipeline(configuration); // builds a TransactionPipeline from config
+var provider = services.BuildServiceProvider();
+
+var pipe = provider.GetRequiredService();
+var (result, ctx) = pipe.Run(new TransactionContext {
+ Customer = new Customer(LoyaltyId: "LOYAL-123", AgeYears: 25),
+ Items = [ new LineItem("SKU-1", 22.97m) ],
+ Tenders = [ new Tender(PaymentKind.Cash, CashGiven: 20m),
+ new Tender(PaymentKind.Card, CardAuthType.Contactless, CardVendor.Visa) ]
+});
+```
+
+3. **Done** — the runtime pipeline is immutable and safe to reuse concurrently.
+
+---
+
+## What’s in the box
+
+### Configuration model
+
+[xref\:PatternKit.Examples.Chain.ConfigDriven.PipelineOptions](xref:PatternKit.Examples.Chain.ConfigDriven.PipelineOptions) drives ordering:
+
+* `DiscountRules`: keys of discount rules to apply **in order**
+* `Rounding`: keys of rounding strategies to apply **in order**
+* `TenderOrder`: optional, informational (e.g., control UI ordering)
+
+Unknown keys are **ignored** (we map by key and skip missing entries).
+
+### Strategies provided
+
+**Discount rules** (keys):
+
+* `discount:cash-2pc` → [xref\:PatternKit.Examples.Chain.ConfigDriven.Cash2Pct](xref:PatternKit.Examples.Chain.ConfigDriven.Cash2Pct)
+ First tender is cash → 2% off `Subtotal`
+* `discount:loyalty-5pc` → [xref\:PatternKit.Examples.Chain.ConfigDriven.Loyalty5Pct](xref:PatternKit.Examples.Chain.ConfigDriven.Loyalty5Pct)
+ Loyalty present → 5% off `Subtotal`
+* `discount:bundle-1off` → [xref\:PatternKit.Examples.Chain.ConfigDriven.Bundle1OffEach](xref:PatternKit.Examples.Chain.ConfigDriven.Bundle1OffEach)
+ Any `BundleKey` with total `Qty ≥ 2` → \$1 off per item in those bundles
+
+**Rounding** (keys):
+
+* `round:charity` → [xref\:PatternKit.Examples.Chain.ConfigDriven.CharityRoundUp](xref:PatternKit.Examples.Chain.ConfigDriven.CharityRoundUp)
+ If any `CHARITY:*` SKU is present → round up to next dollar
+* `round:nickel-cash-only` → [xref\:PatternKit.Examples.Chain.ConfigDriven.NickelCashOnly](xref:PatternKit.Examples.Chain.ConfigDriven.NickelCashOnly)
+ Cash-only transactions → round to nearest \$0.05 (logs “skipped (not cash-only)” otherwise)
+
+**Tender handlers** (DI-registered):
+
+* [xref\:PatternKit.Examples.Chain.ConfigDriven.CashTender](xref:PatternKit.Examples.Chain.ConfigDriven.CashTender) (`tender:cash`)
+* [xref\:PatternKit.Examples.Chain.ConfigDriven.CardTender](xref:PatternKit.Examples.Chain.ConfigDriven.CardTender) (`tender:card`)
+
+> The router itself is assembled by the mediated pipeline pieces; we simply **supply handlers via DI** and the builder wires them into the tender stage.
+
+---
+
+## How it composes
+
+### Discounts & tax (config-driven)
+
+[xref\:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineBuilderExtensions.AddConfigDrivenDiscountsAndTax\*](xref:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineBuilderExtensions.AddConfigDrivenDiscountsAndTax*):
+
+* Recomputes `Subtotal`
+* Iterates `opts.Value.DiscountRules` and applies each rule that exists in the DI map
+* Computes **tax** at **8.75%** of `(Subtotal − DiscountTotal)` and logs `pre-round total`
+
+```csharp
+b.AddConfigDrivenDiscountsAndTax(opts, discountRules);
+```
+
+### Rounding (config-driven)
+
+[xref\:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineBuilderExtensions.AddConfigDrivenRounding\*](xref:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineBuilderExtensions.AddConfigDrivenRounding*):
+
+* Iterates `opts.Value.Rounding` and calls each strategy in order
+* Each strategy decides to apply or log “skipped”
+* Logs final `total`
+
+```csharp
+b.AddConfigDrivenRounding(opts, rounding);
+```
+
+### DI registration
+
+[xref\:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.AddPaymentPipeline\*](xref:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.AddPaymentPipeline*):
+
+* Binds `Payment:Pipeline` to [xref\:PatternKit.Examples.Chain.ConfigDriven.PipelineOptions](xref:PatternKit.Examples.Chain.ConfigDriven.PipelineOptions)
+* Registers:
+
+ * Infra: [xref\:PatternKit.Examples.Chain.IDeviceBus](xref:PatternKit.Examples.Chain.IDeviceBus), [xref\:PatternKit.Examples.Chain.CardProcessors](xref:PatternKit.Examples.Chain.CardProcessors)
+ * Discounts: `Cash2Pct`, `Loyalty5Pct`, `Bundle1OffEach`
+ * Rounding: `CharityRoundUp`, `NickelCashOnly`
+ * Tenders: `CashTender`, `CardTender`
+* Builds a shared [xref\:PatternKit.Examples.Chain.TransactionPipeline](xref:PatternKit.Examples.Chain.TransactionPipeline):
+
+```csharp
+TransactionPipelineBuilder.New()
+ .WithDeviceBus(devices)
+ .AddPreauth()
+ .AddConfigDrivenDiscountsAndTax(opts, discountRules)
+ .AddConfigDrivenRounding(opts, rounding)
+ .WithTenderHandlers(tenderHandlers)
+ .AddTenderHandling()
+ .AddFinalize()
+ .Build();
+```
+
+Consumers receive a thin wrapper: [xref\:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.PaymentPipeline](xref:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.PaymentPipeline) with `Run(ctx)`.
+
+---
+
+## Example scenarios (from tests)
+
+### Mixed tender (cash then card) — **no** nickel rounding
+
+* Config: `Rounding = ["round:nickel-cash-only"]`
+* Items: `Subtotal 22.97 → Tax 2.01 → Pre-round 24.98`
+* Tenders: `$20 cash`, then `Visa` pays the remainder
+
+**Outcome**
+
+* Rounding skipped (not cash-only)
+* Card captures `$4.98`
+* Result: `paid`
+
+See: `TransactionPipelineDemoTests.MixedTender_NoNickelRounding`.
+
+### Cash-only nickel rounding **up** to `$25.00`
+
+* Config: `Rounding = ["round:nickel-cash-only"]`
+* Pre-round total `24.98`
+* Rounding adds `+$0.02`
+* Single cash tender `$25.00` → paid
+
+See: `TransactionPipelineDemoTests.CashOnly_NickelRounding_Up`.
+
+### Charity round-up
+
+* Config: `Rounding = ["round:charity"]`
+* Presence of `CHARITY:RedCross` SKU causes `+$0.02` to next whole dollar
+* Paid by card
+
+See: `TransactionPipelineDemoTests.Charity_RoundUp_Works`.
+
+### Preauth block (age)
+
+* Age-restricted item + underage customer → `TxResult.Fail("age", ...)`
+* Pipeline stops early
+
+See: `TransactionPipelineDemoTests.Preauth_AgeBlock`.
+
+---
+
+## Extending with your own rules/strategies/handlers
+
+1. **Implement** the interface and choose a unique key:
+
+```csharp
+public sealed class Employee10Pct : IDiscountRule
+{
+ public string Key => "discount:employee-10pc";
+ public void Apply(TransactionContext ctx)
+ {
+ if (ctx.Customer.LoyaltyId == "EMP")
+ ctx.AddDiscount(Math.Round(ctx.Subtotal * 0.10m, 2), "employee 10%");
+ }
+}
+```
+
+2. **Register** it:
+
+```csharp
+services.AddSingleton();
+```
+
+3. **Enable** it in config (order is important):
+
+```json
+"Payment": { "Pipeline": { "DiscountRules": [
+ "discount:employee-10pc", "discount:bundle-1off"
+]}}
+```
+
+The same pattern holds for `IRoundingStrategy` and `ITenderHandler`.
+
+---
+
+## FAQ & tips
+
+* **What happens if a key is listed but not registered?**
+ It’s skipped; we only apply rules found in the DI map.
+
+* **Where’s the tax rate?**
+ Inside `AddConfigDrivenDiscountsAndTax` we compute tax at **8.75%**. Swap this with your own calculator if needed.
+
+* **Thread safety?**
+ The composed [xref\:PatternKit.Examples.Chain.TransactionPipeline](xref:PatternKit.Examples.Chain.TransactionPipeline) is immutable and safe for concurrent use. Builders are not thread-safe.
+
+* **Observability**
+ Every rule/strategy logs its work to `ctx.Log` using concise, human-readable entries suitable for unit tests and diagnostics.
+
+* **Performance**
+
+ * All composition happens **once** at startup.
+ * Execution uses arrays and `static` delegates where possible to minimize allocations.
+ * The config-driven action chains still short-circuit *inside* each component when appropriate.
+
+---
+
+## Reference
+
+* Composition
+
+ * [xref\:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineBuilderExtensions](xref:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineBuilderExtensions)
+ * [xref\:PatternKit.Examples.Chain.TransactionPipelineBuilder](xref:PatternKit.Examples.Chain.TransactionPipelineBuilder)
+ * [xref\:PatternKit.Examples.Chain.TransactionPipeline](xref:PatternKit.Examples.Chain.TransactionPipeline)
+* Config & DI
+
+ * [xref\:PatternKit.Examples.Chain.ConfigDriven.PipelineOptions](xref:PatternKit.Examples.Chain.ConfigDriven.PipelineOptions)
+ * [xref\:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.AddPaymentPipeline\*](xref:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.AddPaymentPipeline*)
+ * [xref\:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.PaymentPipeline](xref:PatternKit.Examples.Chain.ConfigDriven.ConfigDrivenPipelineDemo.PaymentPipeline)
+* Strategies
+
+ * Discounts: [xref\:PatternKit.Examples.Chain.ConfigDriven.Cash2Pct](xref:PatternKit.Examples.Chain.ConfigDriven.Cash2Pct), [xref\:PatternKit.Examples.Chain.ConfigDriven.Loyalty5Pct](xref:PatternKit.Examples.Chain.ConfigDriven.Loyalty5Pct), [xref\:PatternKit.Examples.Chain.ConfigDriven.Bundle1OffEach](xref:PatternKit.Examples.Chain.ConfigDriven.Bundle1OffEach)
+ * Rounding: [xref\:PatternKit.Examples.Chain.ConfigDriven.CharityRoundUp](xref:PatternKit.Examples.Chain.ConfigDriven.CharityRoundUp), [xref\:PatternKit.Examples.Chain.ConfigDriven.NickelCashOnly](xref:PatternKit.Examples.Chain.ConfigDriven.NickelCashOnly)
+ * Tenders: [xref\:PatternKit.Examples.Chain.ConfigDriven.CashTender](xref:PatternKit.Examples.Chain.ConfigDriven.CashTender), [xref\:PatternKit.Examples.Chain.ConfigDriven.CardTender](xref:PatternKit.Examples.Chain.ConfigDriven.CardTender)
+* Domain
+
+ * [xref\:PatternKit.Examples.Chain.TransactionContext](xref:PatternKit.Examples.Chain.TransactionContext), [xref\:PatternKit.Examples.Chain.TxResult](xref:PatternKit.Examples.Chain.TxResult),
+ [xref\:PatternKit.Examples.Chain.Tender](xref:PatternKit.Examples.Chain.Tender), [xref\:PatternKit.Examples.Chain.LineItem](xref:PatternKit.Examples.Chain.LineItem), [xref\:PatternKit.Examples.Chain.Customer](xref:PatternKit.Examples.Chain.Customer)
diff --git a/docs/examples/index.md b/docs/examples/index.md
new file mode 100644
index 0000000..f7bed25
--- /dev/null
+++ b/docs/examples/index.md
@@ -0,0 +1,78 @@
+# Examples & Demos
+
+Welcome! This section collects small, focused demos that show **how to compose behaviors with PatternKit**—without sprawling frameworks, if/else ladders, or tangled control flow. Each demo is production-shaped, tiny, and easy to lift into your own code.
+
+## What you’ll see
+
+* **First-match-wins strategies** for branching without `if` chains.
+* **Branchless action chains** for rule packs (logging, pre-auth, discounts, tax).
+* **Pipelines** built declaratively and tested end-to-end.
+* **Config-driven composition** (DI + `IOptions`) so ops can re-order rules without redeploys.
+* **Strategy-based coercion** for turning “whatever came in” into the types you actually want.
+* **Ultra-minimal HTTP routing** to illustrate middleware vs. routes vs. negotiation.
+
+## Demos in this section
+
+* **Composed, Preference-Aware Notification Strategy (Email/SMS/Push/IM)**
+ Shows how to layer a user’s channel preferences, failover, and throttling into a composable **Strategy** without `switch`es. Good template for “try X, else Y” flows (alerts, KYC, etc.).
+
+* **Auth & Logging Chain**
+ A tiny `ActionChain` showing **request ID logging**, an **auth short-circuit** for `/admin/*`, and the subtleties of **`.ThenContinue` vs `.ThenStop` vs `Finally`** (strict-stop semantics by default).
+
+* **Strategy-Based Data Coercion**
+ `Coercer` compiles a tiny set of **TryStrategy** handlers once per target type to coerce JSON/primitives/strings at runtime—**first-match-wins**, culture-safe, allocation-light.
+
+* **Mediated Transaction Pipeline**
+ An in-code pipeline (no config) that demonstrates **ActionChain**, **Strategy**, and **TryStrategy** together: pre-auth checks, discounting, tax, rounding, tender handling, and finalization. Emphasizes clear logs and testable rules.
+
+* **Configuration-Driven Transaction Pipeline**
+ Same business shape as above, but wired via DI + `IOptions`. Discounts/rounding/tenders are discovered and **ordered from config**, making the pipeline operationally tunable.
+
+* **Minimal Web Request Router**
+ A tiny “API gateway” that separates **first-match middleware** (side effects/logging/auth) from **first-match routes** and **content negotiation**. A crisp example of Strategy patterns in an HTTP-ish setting.
+
+## How to run
+
+From the repo root:
+
+```bash
+# Build everything
+dotnet build PatternKit.slnx -c Release
+
+# Run all tests (quick, cross-targeted)
+dotnet test PatternKit.slnx -c Release
+```
+
+> Tip (Linux/macOS): our tests force `en-US` culture to make currency/text output stable across platforms.
+> If you run outside the test host, ensure invariant globalization isn’t enabled:
+>
+> * `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT` should be **unset** or **0**.
+> * To see console output, execute demo entrypoints (e.g., `Demo.Run`) from your IDE or a small console host.
+
+## Design highlights
+
+* **First-match wins** everywhere (middleware, routes, rounding rules, tender routers).
+* **Zero-`if` routing** via `BranchBuilder` and delegates (`Predicate` → `Handler`).
+* **Branchless rule packs** via `ActionChain` with `When/ThenContinue/ThenStop/Finally`.
+* **Immutable, thread-safe artifacts** after `.Build()`; builders remain mutable.
+* **Tiny domain types** you can replace or extend (requests, responses, tenders, items, rules).
+
+## Where to look (quick map)
+
+* **Auth & Logging Chain:** `AuthLoggingDemo` (+ `AuthLoggingDemoTests`) — strict-stop auth with logging.
+* **Coercer:** `Coercer` (+ `CoercerTests`) — strategy-based, culture-safe coercion.
+* **Mini Router:** `MiniRouter` + `Demo.Run` — middleware/auth/negotiation in console output.
+* **Mediated Pipeline:** `TransactionPipelineBuilder` + `MediatedTransactionPipelineDemo.Run`.
+* **Config-Driven Pipeline:** `ConfigDrivenPipelineDemo.AddPaymentPipeline` + `PipelineOptions`.
+* **Tests:** `PatternKit.Examples.Tests/*` use TinyBDD scenarios that read like specs.
+
+## Why these demos exist
+
+They’re meant to be **copy-pasteable patterns**:
+
+* Replace cascade `if/else` with **composed strategies**.
+* Turn scattered rules into a **linear, testable chain**.
+* Move “what runs & in what order” to **configuration**, when appropriate.
+* Keep the primitives small so the system stays legible under change.
+
+Jump in via the pages in the left-hand ToC and open the corresponding test files—the assertions double as executable documentation.
\ No newline at end of file
diff --git a/docs/examples/mediated-transaction-pipeline.md b/docs/examples/mediated-transaction-pipeline.md
new file mode 100644
index 0000000..da6f43c
--- /dev/null
+++ b/docs/examples/mediated-transaction-pipeline.md
@@ -0,0 +1,280 @@
+# Mediated transaction pipeline (demo)
+
+> **TL;DR**
+> This sample shows how to build a production-grade checkout pipeline by **composing small, testable stages**.
+> We combine:
+>
+> * **Action chains** for branchless pre-auth, discounts, and tax
+> * A **BranchBuilder-powered router** for tender handling
+> * A tiny **rounding rule engine** (first-match-wins)
+> * A lightweight **pipeline runner** with **short-circuit** semantics
+
+Everything runs on in-memory types so you can exercise it entirely from unit tests.
+
+---
+
+## What we’re building
+
+The pipeline transforms a mutable [xref\:PatternKit.Examples.Chain.TransactionContext](xref:PatternKit.Examples.Chain.TransactionContext) through a series of stages and returns a terminal [xref\:PatternKit.Examples.Chain.TxResult](xref:PatternKit.Examples.Chain.TxResult):
+
+```
+[Preauth] → [Discounts & Tax] → [Rounding] → [Tender Handling] → [Finalize]
+ | | | |
+ v v v v
+ updates ctx updates ctx updates ctx sets terminal
+ (no return) (no return) (no return) TxResult + beeps
+```
+
+* Each stage has signature `bool Stage(TransactionContext ctx)` — **true** = continue, **false** = stop.
+* Stages mutate `ctx` (totals, logs, tender progress) and may set `ctx.Result` to a terminal value.
+
+---
+
+## Core concepts
+
+### The `Stage` delegate and the pipeline runner
+
+* `Stage` is just: **mutate ctx, decide continue/stop**.
+* [xref\:PatternKit.Examples.Chain.TransactionPipeline](xref:PatternKit.Examples.Chain.TransactionPipeline) runs the ordered stages and enforces a terminal result:
+
+ * If any stage returns **false**: stop and return the current `ctx.Result`.
+ * If no stage stopped and `ctx.Result` is still null: force success `("paid", "paid in full")`.
+
+```csharp
+var (result, finalCtx) = new TransactionPipeline(stages).Run(ctx);
+```
+
+### Adapting action chains to stages
+
+We use [xref\:PatternKit.Behavioral.Chain.ActionChain%601](xref:PatternKit.Behavioral.Chain.ActionChain%601) for **branchless** logic and adapt it to a `Stage` via `ChainStage.From(chain)`.
+If an action chain sets a failing `ctx.Result`, the adapted stage returns **false** to short-circuit the pipeline.
+
+---
+
+## Building the pipeline
+
+Use [xref\:PatternKit.Examples.Chain.TransactionPipelineBuilder](xref:PatternKit.Examples.Chain.TransactionPipelineBuilder) to declaratively assemble stages:
+
+```csharp
+var pipeline = TransactionPipelineBuilder.New()
+ .AddPreauth() // age restriction, empty basket
+ .AddDiscountsAndTax() // cash 2%, loyalty 5%, coupons, bundle, then tax
+ .AddRounding() // first matching rounding rule
+ .AddTenderHandling() // cash + card handlers via BranchBuilder router
+ .AddFinalize() // terminal result + device beep
+ .Build();
+
+var (result, ctx) = pipeline.Run(transactionCtx);
+```
+
+### 1) Pre-authorization (no `if/else`)
+
+Implemented as an **ActionChain** that stops on the first failing rule:
+
+* Age restricted items require `Customer.AgeYears ≥ 21`
+* Basket must not be empty
+
+On failure: sets `ctx.Result = TxResult.Fail(...)`, logs the reason, **stops** the pipeline.
+
+### 2) Discounts & tax (no `if/else`)
+
+Also an **ActionChain**:
+
+* Cash-first: **2%** off
+* Loyalty present: **5%** off
+* Manufacturer coupons (sum per item × qty)
+* In-house coupons
+* Bundle deal: items sharing a `BundleKey` with total `Qty ≥ 2` → `$1 off` per unit in the bundle
+* Then compute tax at **8.75%** of `(Subtotal - DiscountTotal)`
+
+All values are rounded to two decimals at the point of application.
+
+### 3) Rounding rules (first-match-wins)
+
+[xref\:PatternKit.Examples.Chain.RoundingPipeline](xref:PatternKit.Examples.Chain.RoundingPipeline) evaluates `IRoundingRule` implementations **in order** and applies the first that matches:
+
+* [xref\:PatternKit.Examples.Chain.CharityRoundUpRule](xref:PatternKit.Examples.Chain.CharityRoundUpRule) — if any `Sku` starts with `CHARITY:`, round up to next dollar and notify [xref\:PatternKit.Examples.Chain.ICharityTracker](xref:PatternKit.Examples.Chain.ICharityTracker)
+* [xref\:PatternKit.Examples.Chain.NickelCashOnlyRule](xref:PatternKit.Examples.Chain.NickelCashOnlyRule) — if *all tenders are cash* and a `ROUND:NICKEL` SKU is present, round to nearest \$0.05
+
+Every rule logs the delta and the new total when applied.
+
+You can override rules with:
+
+```csharp
+TransactionPipelineBuilder.New()
+ .WithRoundingRules(new MyRule(), new NickelCashOnlyRule())
+ ...
+```
+
+### 4) Tender handling (BranchBuilder router)
+
+Handlers live behind a **zero-`if` router** built by [xref\:PatternKit.Examples.Chain.TenderRouterFactory](xref:PatternKit.Examples.Chain.TenderRouterFactory):
+
+* Each handler implements `ITenderHandler` (from `ConfigDriven` sample) with:
+
+ * `CanHandle(ctx, tender) : bool`
+ * `Handle(ctx, tender) : TxResult`
+* The router is composed with [xref\:PatternKit.Creational.Builder.BranchBuilder\`2](xref:PatternKit.Creational.Builder.BranchBuilder`2):
+
+ * Evaluate handlers **in order**
+ * First predicate that returns **true** runs its handler
+ * If none match, return `TxResult.Fail("route", ...)`
+
+By default (if you don’t call `WithTenderHandlers(...)`) we register:
+
+* `CashTender` — opens drawer, applies cash, calculates change
+* `CardTender` — resolves a processor by vendor, `Authorize` then `Capture`, applies payment
+
+You can supply your own:
+
+```csharp
+builder.WithTenderHandlers(new MyGiftCardHandler(), new CashTender(devices));
+```
+
+### 5) Finalization
+
+If no stage failed:
+
+* If `ctx.RemainderDue > 0`: `Fail("insufficient", "...")`
+* Else: `Success("paid", "paid in full")` and `devices.Beep("printer", 2)`
+
+Always logs `"done."`.
+
+---
+
+## End-to-end scenario (from tests)
+
+
+- **Feature:** Cash + loyalty + two cigarettes
+ - **Given:** customer age 25, loyalty `"LOYAL-123"`, tenders: `$50` cash, items:
+ * `CIGS` \$10.96 × 1 (age-restricted, bundle `CIGS`)
+ * `CIGS` \$10.97 × 1 (age-restricted, bundle `CIGS`)
+ - **Then** the pipeline computes:
+ * Subtotal = **21.93**
+ * Discounts:
+
+ * Cash 2% = **0.44**
+ * Loyalty 5% = **1.10**
+ * Bundle deal = **2.00**
+ * **Total discounts = 3.54**
+ * Tax (8.75% of 21.93 − 3.54 = 18.39) = **1.61**
+ * Grand total = **20.00**
+ * Cash given 50 → Change = **30.00**
+ * Terminal result = **Ok=true, Code="paid"**
+ * Log contains: `"preauth: ok"`, individual discount entries, `"tax:"`, `"total:"`, and `"done."`
+
+> This is codified in `MediatedTransactionPipelineDemoTests` using TinyBDD.
+
+---
+
+## Extensibility points
+
+* **Devices:** `.WithDeviceBus(IDeviceBus)` (beeper, cash drawer, etc.)
+* **Tender handlers:** `.WithTenderHandlers(params ITenderHandler[])`
+* **Rounding rules:** `.WithRoundingRules(params IRoundingRule[])`
+* **Arbitrary stages:** `.AddStage(Stage)` or `.AddStage(ActionChain)`
+
+Because stages are just delegates, you can encapsulate feature slices (fraud checks, gift cards, store credit, EBT, etc.) as separate assemblies and drop them into the builder.
+
+---
+
+## Routing without `if/else`
+
+The tender router is built once and runs hot:
+
+```csharp
+public static TenderRouter Build(IEnumerable handlers)
+{
+ var bb = BranchBuilder.Create();
+
+ foreach (var h in handlers)
+ bb.Add(
+ (in c, in t) => h.CanHandle(c, t), // predicate
+ (c, in t) => h.Handle(c, t)); // handler
+
+ return bb.Build(
+ fallbackDefault: static (ctx, in t)
+ => TxResult.Fail("route", $"no handler for {t.Kind}"),
+ projector: static (preds, steps, _, def) => (ctx, in t) =>
+ {
+ for (var i = 0; i < preds.Length; i++)
+ if (preds[i](in ctx, in t)) return steps[i](ctx, in t);
+ return def(ctx, in t);
+ });
+}
+```
+
+* **Zero allocations** per call (no lambdas captured, all signatures are exact).
+* **Short-circuit** on first match.
+
+---
+
+## Performance notes
+
+* Most delegates are `static` and use **`in` parameters** to avoid copies.
+* Arithmetic is rounded at the **moment of application** to keep totals stable.
+* The pipeline, chains, and router are **immutable** after `Build()`; safe for concurrent use.
+
+---
+
+## Running the demo programmatically
+
+If you just want the sensible defaults:
+
+```csharp
+var ctx = new TransactionContext
+{
+ Customer = new Customer(LoyaltyId: "LOYAL-123", AgeYears: 25),
+ Tender = new Tender(PaymentKind.Cash, CashGiven: 50m),
+ Items =
+ [
+ new LineItem("CIGS", 10.96m, Qty: 1, AgeRestricted: true, BundleKey: "CIGS"),
+ new LineItem("CIGS", 10.97m, Qty: 1, AgeRestricted: true, BundleKey: "CIGS"),
+ ]
+};
+
+var (result, finalCtx) = MediatedTransactionPipelineDemo.Run(ctx);
+// result.Ok == true, result.Code == "paid"
+```
+
+---
+
+## Troubleshooting
+
+* **Pipeline stops early**
+ Check `ctx.Result` and `ctx.Log`. Any stage can set a failing result and return `false` to short-circuit.
+
+* **Totals don’t add up**
+ Ensure you call `RecomputeSubtotal()` before computing discounts and tax (the included chain does this first).
+
+* **Rounding not applied**
+ Confirm rule order and `ShouldApply` conditions. Only the **first** matching rule runs.
+
+* **Tender not handled**
+ Verify handler order and `CanHandle` predicates. The router is **first-match-wins**; add a fallback handler or rely on the built-in `"route"` failure.
+
+---
+
+## API reference (selected)
+
+* Pipeline
+
+ * [xref\:PatternKit.Examples.Chain.TransactionPipeline](xref:PatternKit.Examples.Chain.TransactionPipeline)
+ * [xref\:PatternKit.Examples.Chain.TransactionPipelineBuilder](xref:PatternKit.Examples.Chain.TransactionPipelineBuilder)
+ * [xref\:PatternKit.Examples.Chain.MediatedTransactionPipelineDemo](xref:PatternKit.Examples.Chain.MediatedTransactionPipelineDemo)
+* Domain types & services
+
+ * [xref\:PatternKit.Examples.Chain.TransactionContext](xref:PatternKit.Examples.Chain.TransactionContext), [xref\:PatternKit.Examples.Chain.TxResult](xref:PatternKit.Examples.Chain.TxResult), [xref\:PatternKit.Examples.Chain.Tender](xref:PatternKit.Examples.Chain.Tender), [xref\:PatternKit.Examples.Chain.LineItem](xref:PatternKit.Examples.Chain.LineItem), [xref\:PatternKit.Examples.Chain.Customer](xref:PatternKit.Examples.Chain.Customer)
+ * [xref\:PatternKit.Examples.Chain.IDeviceBus](xref:PatternKit.Examples.Chain.IDeviceBus), [xref\:PatternKit.Examples.Chain.ICardProcessor](xref:PatternKit.Examples.Chain.ICardProcessor), [xref\:PatternKit.Examples.Chain.CardProcessors](xref:PatternKit.Examples.Chain.CardProcessors)
+* Rounding
+
+ * [xref\:PatternKit.Examples.Chain.IRoundingRule](xref:PatternKit.Examples.Chain.IRoundingRule), [xref\:PatternKit.Examples.Chain.CharityRoundUpRule](xref:PatternKit.Examples.Chain.CharityRoundUpRule), [xref\:PatternKit.Examples.Chain.NickelCashOnlyRule](xref:PatternKit.Examples.Chain.NickelCashOnlyRule), [xref\:PatternKit.Examples.Chain.RoundingPipeline](xref:PatternKit.Examples.Chain.RoundingPipeline)
+* Tender routing
+
+ * [xref\:PatternKit.Examples.Chain.TenderRouterFactory](xref:PatternKit.Examples.Chain.TenderRouterFactory)
+ * [xref\:PatternKit.Creational.Builder.BranchBuilder\`2](xref:PatternKit.Creational.Builder.BranchBuilder`2)
+ * [xref\:PatternKit.Creational.Builder.ChainBuilder\`1](xref:PatternKit.Creational.Builder.ChainBuilder`1)
+* Chains
+
+ * [xref\:PatternKit.Behavioral.Chain.ActionChain%601](xref:PatternKit.Behavioral.Chain.ActionChain%601) (used via `ChainStage.From(...)`)
+
diff --git a/docs/examples/mini-router.md b/docs/examples/mini-router.md
new file mode 100644
index 0000000..214b3a7
--- /dev/null
+++ b/docs/examples/mini-router.md
@@ -0,0 +1,273 @@
+# MiniRouter — a tiny, composable API gateway/router
+
+> **TL;DR**
+> `MiniRouter` shows how three PatternKit primitives compose into a pragmatic HTTP-ish pipeline:
+>
+> * **Middleware** (side-effects, first-match-wins) — built with [xref\:PatternKit.Behavioral.Strategy.ActionStrategy\`1](xref:PatternKit.Behavioral.Strategy.ActionStrategy`1)
+> * **Routes** (return a response, first-match-wins) — built with [xref\:PatternKit.Behavioral.Strategy.Strategy\`2](xref:PatternKit.Behavioral.Strategy.Strategy`2)
+> * **Content negotiation** (try handlers until one succeeds) — built with [xref\:PatternKit.Behavioral.Strategy.TryStrategy\`2](xref:PatternKit.Behavioral.Strategy.TryStrategy`2)
+
+This sample lives in `PatternKit.Examples.ApiGateway` and is intentionally tiny so you can lift it into tests, demos, or “just-enough” services.
+
+---
+
+## What it is (and isn’t)
+
+**MiniRouter** is not a web framework. It’s a **pure-function** pipeline you can exercise from unit tests or swap behind your existing transport (ASP.NET, Minimal APIs, Lambda, etc.). It demonstrates:
+
+* A **first-match-wins** mindset across middleware and routing
+* **Short-circuit** behavior (e.g., auth checks) without exceptions
+* **Allocation-light** execution with by-`in` parameters and static lambdas
+* Clean separation of **effects** (middleware) and **results** (routes)
+
+---
+
+## The primitives
+
+### Request/Response
+
+* [xref\:PatternKit.Examples.ApiGateway.Request](xref:PatternKit.Examples.ApiGateway.Request) is a tiny immutable input: `Method`, `Path`, `Headers`, `Body?`
+* [xref\:PatternKit.Examples.ApiGateway.Response](xref:PatternKit.Examples.ApiGateway.Response) is an immutable output: `StatusCode`, `ContentType`, `Body`
+* [xref\:PatternKit.Examples.ApiGateway.Responses](xref:PatternKit.Examples.ApiGateway.Responses) holds helpers: `Text`, `Json`, `NotFound`, `Unauthorized`
+
+### Router
+
+* [xref\:PatternKit.Examples.ApiGateway.MiniRouter](xref:PatternKit.Examples.ApiGateway.MiniRouter) composes three strategies:
+
+ * `_middleware : ActionStrategy` — **fire-and-forget** side effects (logging, metrics, auth messages)
+ * `_routes : Strategy` — **produce** a `Response`
+ * `_negotiate : TryStrategy` — **select** a `Content-Type` if a route left it blank
+
+---
+
+## Building a router
+
+Use the fluent builder:
+
+```csharp
+var router = MiniRouter.Create()
+ // --- middleware (first-match-wins) ---
+ .Use(
+ static (in r) => r.Headers.ContainsKey("X-Request-Id"),
+ static (in r) => Console.WriteLine($"reqid={r.Headers["X-Request-Id"]}"))
+ .Use(
+ static (in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal) &&
+ !r.Headers.ContainsKey("Authorization"),
+ static (in _) => Console.WriteLine("Denied: missing Authorization"))
+
+ // --- routes (first-match-wins) ---
+ .Map(
+ static (in r) => r is { Method: "GET", Path: "/health" },
+ static (in _) => Responses.Text(200, "OK"))
+ .Map(
+ static (in r) => r.Method == "GET" && r.Path.StartsWith("/users/", StringComparison.Ordinal),
+ static (in r) =>
+ {
+ var idStr = r.Path["/users/".Length..];
+ return int.TryParse(idStr, out var id)
+ ? Responses.Json(200, $"{{\"id\":{id},\"name\":\"user{id}\"}}")
+ : Responses.Text(404, "User not found");
+ })
+ .Map(
+ static (in r) => r is { Method: "POST", Path: "/users" },
+ static (in _) => Responses.Json(201, "{\"ok\":true}"))
+
+ // auth route fallback (denied)
+ .Map(
+ static (in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal) &&
+ !r.Headers.ContainsKey("Authorization"),
+ static (in _) => Responses.Unauthorized())
+
+ // default
+ .NotFound(static (in _) => Responses.NotFound())
+ .Build();
+```
+
+**Notes**
+
+* **Middleware** uses [xref\:PatternKit.Behavioral.Strategy.ActionStrategy\`1](xref:PatternKit.Behavioral.Strategy.ActionStrategy`1): *first* matching action runs; others are skipped.
+* **Routes** use [xref\:PatternKit.Behavioral.Strategy.Strategy\`2](xref:PatternKit.Behavioral.Strategy.Strategy`2): *first* matching handler returns a `Response`.
+* A **default route** is set via `.NotFound(...)`.
+* The builder sets a **noop default middleware** so `.Handle` never throws due to “no middleware.”
+
+---
+
+## Content negotiation
+
+If a route returns an empty `ContentType`, **MiniRouter** will ask the negotiator to supply one. The **default negotiator**:
+
+1. If `Accept: application/json` → `application/json; charset=utf-8`
+2. Else if `Accept: text/plain` → `text/plain; charset=utf-8`
+3. Else → default to JSON
+
+You can provide your own:
+
+```csharp
+var neg = TryStrategy.Create()
+ .Always(static (in r, out string? ct) =>
+ {
+ if (r.Headers.TryGetValue("Accept", out var a) && a.Contains("application/xml"))
+ { ct = "application/xml; charset=utf-8"; return true; }
+ ct = null; return false;
+ })
+ .Finally(static (in _, out string? ct) => { ct = "application/json; charset=utf-8"; return true; })
+ .Build();
+
+var router = MiniRouter.Create()
+ // ... Use/Map/NotFound ...
+ .WithNegotiator(neg)
+ .Build();
+```
+
+---
+
+## Demo walkthrough
+
+`Demo.Run()` wires the router, then simulates requests:
+
+```csharp
+Print(router.Handle(new Request("GET", "/health", commonHeaders)));
+Print(router.Handle(new Request("GET", "/users/42", commonHeaders)));
+Print(router.Handle(new Request("GET", "/users/abc", commonHeaders)));
+Print(router.Handle(new Request("GET", "/admin/metrics", new Dictionary()))); // unauthorized
+Print(router.Handle(new Request("POST", "/users", commonHeaders, "{\"name\":\"Ada\"}")));
+Print(router.Handle(new Request("GET", "/nope", commonHeaders)));
+```
+
+**Illustrative output** (order matters—note the middleware log before the 401):
+
+```
+200 text/plain; charset=utf-8
+OK
+
+200 application/json; charset=utf-8
+{"id":42,"name":"user42"}
+
+404 text/plain; charset=utf-8
+User not found
+
+Denied: missing Authorization
+401 text/plain; charset=utf-8
+Unauthorized
+
+201 application/json; charset=utf-8
+{"ok":true}
+
+404 text/plain; charset=utf-8
+Not Found
+```
+
+---
+
+## Why three strategies?
+
+| Concern | Type | Behavior | Why here |
+| ----------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | --------------------------------------------- |
+| Middleware | [xref\:PatternKit.Behavioral.Strategy.ActionStrategy\`1](xref:PatternKit.Behavioral.Strategy.ActionStrategy`1) | First matching **action** runs (no return). | Logging, metrics, auth *messages*, CORS, etc. |
+| Routing | [xref\:PatternKit.Behavioral.Strategy.Strategy\`2](xref:PatternKit.Behavioral.Strategy.Strategy`2) | First matching **handler** returns a value. | Pick a `Response` for the request. |
+| Negotiation | [xref\:PatternKit.Behavioral.Strategy.TryStrategy\`2](xref:PatternKit.Behavioral.Strategy.TryStrategy`2) | Chain **try** handlers until one succeeds. | Fill in `ContentType` if missing. |
+
+All three are allocation-light, immutable once built, and thread-safe to execute.
+
+---
+
+## Testing with TinyBDD
+
+We ship BDD-style tests in `PatternKit.Examples.Tests.ApiGateway`.
+
+### Health check
+
+```csharp
+await Given("a default router", DefaultRouter)
+ .When("GET /health", r => r.Handle(new Request("GET", "/health", Headers())))
+ .Then("status is 200", res => res.StatusCode == 200)
+ .And("content-type is text/plain", res => res.ContentType.StartsWith("text/plain"))
+ .AssertPassed();
+```
+
+### Middleware first-match wins
+
+```csharp
+var hits = new List();
+
+MiniRouter Build()
+ => MiniRouter.Create()
+ .Use(static (in r) => r.Path.StartsWith("/a"), (in _) => hits.Add("A"))
+ .Use(static (in r) => r.Path.StartsWith("/a"), (in _) => hits.Add("B")) // also matches but won't run
+ .Map(static (in _) => true, static (in _) => Responses.Text(200, "ok"))
+ .NotFound(static (in _) => Responses.NotFound())
+ .Build();
+
+await Given("two matching middleware", Build)
+ .When("GET /a", r => r.Handle(new Request("GET", "/a", Headers())))
+ .Then("exactly one ran", _ => hits.Count == 1 && hits[0] == "A")
+ .AssertPassed();
+```
+
+### Content negotiation
+
+```csharp
+await Given("negotiating router", NegotiatingRouter)
+ .When("GET /neg with Accept: text/plain",
+ r => r.Handle(new Request("GET", "/neg", Headers(accept: "text/plain"))))
+ .Then("content-type is text/plain", res => res.ContentType.StartsWith("text/plain"))
+ .AssertPassed();
+```
+
+---
+
+## Extending MiniRouter
+
+* **Add middleware**: `.Use(predicate, action)` — only the **first** matching action runs.
+* **Add routes**: `.Map(predicate, handler)` — only the **first** matching handler returns.
+* **Change NotFound**: `.NotFound(handler)` — default when nothing matches.
+* **Swap negotiator**: `.WithNegotiator(tryStrategy)` — e.g., add `application/xml`.
+
+**Tip:** Prefer **static method groups** or **static lambdas** for zero-capture delegates and better allocations.
+
+---
+
+## Performance notes
+
+* By-`in` parameters on strategies avoid defensive copies for structs.
+* Static lambdas (`static (in r) => ...`) prevent hidden captures/allocations.
+* Immutable, pre-built pipelines are **thread-safe**; builders are not.
+
+---
+
+## Troubleshooting
+
+* **Multiple middleware actions run**
+ Ensure your conditions don’t both match earlier branches. **First match wins**; later branches are skipped only if an earlier branch matched.
+
+* **Route not hit**
+ Check earlier `.Map` conditions—an earlier, broader predicate may be capturing the request.
+
+* **Missing content-type**
+ If a handler returns `Response` with an empty `ContentType`, the negotiator fills it. Provide your own via `.WithNegotiator(...)` if defaults don’t suit.
+
+---
+
+## API reference
+
+* [xref\:PatternKit.Examples.ApiGateway.MiniRouter](xref:PatternKit.Examples.ApiGateway.MiniRouter)
+
+ * [xref\:PatternKit.Examples.ApiGateway.MiniRouter.Create\*](xref:PatternKit.Examples.ApiGateway.MiniRouter.Create*)
+ * [xref\:PatternKit.Examples.ApiGateway.MiniRouter.Handle\*](xref:PatternKit.Examples.ApiGateway.MiniRouter.Handle*)
+ * [xref\:PatternKit.Examples.ApiGateway.MiniRouter.Builder](xref:PatternKit.Examples.ApiGateway.MiniRouter.Builder)
+* [xref\:PatternKit.Examples.ApiGateway.Request](xref:PatternKit.Examples.ApiGateway.Request) / [xref\:PatternKit.Examples.ApiGateway.Response](xref:PatternKit.Examples.ApiGateway.Response) / [xref\:PatternKit.Examples.ApiGateway.Responses](xref:PatternKit.Examples.ApiGateway.Responses)
+* [xref\:PatternKit.Behavioral.Strategy.ActionStrategy\`1](xref:PatternKit.Behavioral.Strategy.ActionStrategy`1)
+* [xref\:PatternKit.Behavioral.Strategy.Strategy\`2](xref:PatternKit.Behavioral.Strategy.Strategy`2)
+* [xref\:PatternKit.Behavioral.Strategy.TryStrategy\`2](xref:PatternKit.Behavioral.Strategy.TryStrategy`2)
+
+---
+
+### Appendix: End-to-end demo
+
+Run:
+
+```csharp
+PatternKit.Examples.ApiGateway.Demo.Run();
+```
+
+It prints the sequence described above (health, users/42, users/abc, admin unauthorized with a middleware log line first, POST /users, and 404 for /nope).
diff --git a/docs/examples/toc.yml b/docs/examples/toc.yml
new file mode 100644
index 0000000..7d1928a
--- /dev/null
+++ b/docs/examples/toc.yml
@@ -0,0 +1,20 @@
+- name: Examples & Demos
+ href: index.md
+
+- name: Auth & Logging with `ActionChain`
+ href: auth-logging-chain.md
+
+- name: Strategy-Based Data Coercion
+ href: coercer.md
+
+- name: Composed, Preference-Aware Notification Strategy (Email/SMS/Push/IM)
+ href: composed-notification-strategy.md
+
+- name: Mediated Transaction Pipeline
+ href: mediated-transaction-pipeline.md
+
+- name: Configuration-Driven Transaction Pipeline
+ href: config-driven-transaction-pipeline.md
+
+- name: Minimal Web Request Router
+ href: mini-router.md
\ No newline at end of file
diff --git a/docs/patterns/behavioral/chain/actionchain.md b/docs/patterns/behavioral/chain/actionchain.md
new file mode 100644
index 0000000..d94ac3b
--- /dev/null
+++ b/docs/patterns/behavioral/chain/actionchain.md
@@ -0,0 +1,179 @@
+# Behavioral.Chain.ActionChain
+
+**ActionChain\** is a tiny, middleware-style pipeline for “branchless rule packs.”
+Each step receives the current context and a `next` delegate; it can **continue** or **short-circuit**.
+
+Use it when you want *ordered rules* (logging, validation, pre-auth, pricing, etc.) without `if` ladders, and when you
+need very explicit continue/stop semantics.
+
+---
+
+## TL;DR
+
+```csharp
+using PatternKit.Behavioral.Chain;
+
+var log = new List();
+
+var chain = ActionChain.Create()
+ .When((in r) => r.Headers.ContainsKey("X-Request-Id"))
+ .ThenContinue(r => log.Add($"reqid={r.Headers["X-Request-Id"]}"))
+
+ .When((in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal) &&
+ !r.Headers.ContainsKey("Authorization"))
+ .ThenStop(r => log.Add("deny: missing auth"))
+
+ // Tail runs only if earlier steps called `next`
+ .Finally((in r, next) =>
+ {
+ log.Add($"{r.Method} {r.Path}");
+ next(in r); // terminal `next` is a no-op
+ })
+ .Build();
+
+chain.Execute(new HttpRequest("GET", "/health", new Dictionary()));
+chain.Execute(new HttpRequest("GET", "/admin/metrics", new Dictionary()));
+// => ["GET /health", "deny: missing auth", "GET /admin/metrics"]
+```
+
+---
+
+## Why ActionChain?
+
+* **Linear, readable rule packs**: each rule says *when* it applies and *what* it does.
+* **Strict stop by default**: if a handler doesn’t call `next`, the chain ends immediately.
+* **Low overhead**: builds a single, composed delegate; `Execute` is just one call.
+* **Perf-shaped**: `in` parameters avoid copies; no LINQ; minimal allocations after `Build()`.
+
+---
+
+## Core API
+
+```csharp
+public sealed class ActionChain
+{
+ public delegate void Next(in TCtx ctx);
+ public delegate void Handler(in TCtx ctx, Next next);
+ public delegate bool Predicate(in TCtx ctx);
+
+ public void Execute(in TCtx ctx);
+
+ public static Builder Create();
+
+ public sealed class Builder
+ {
+ // Always-run middleware (if continued)
+ public Builder Use(Handler handler);
+
+ // Conditional block
+ public WhenBuilder When(Predicate predicate);
+
+ // Tail handler (runs only if chain wasn’t short-circuited)
+ public Builder Finally(Handler tail);
+
+ public ActionChain Build();
+ }
+
+ public sealed class WhenBuilder
+ {
+ // Run custom handler when predicate is true; else continue automatically
+ public Builder Do(Handler handler);
+
+ // Run action and STOP the chain when predicate is true
+ public Builder ThenStop(Action action);
+
+ // Run action and CONTINUE when predicate is true
+ public Builder ThenContinue(Action action);
+ }
+}
+```
+
+### Semantics (important!)
+
+* **Strict stop**: Any handler can end the chain by not calling `next`.
+ This also **skips `Finally`**. If you truly need “always run,” split logging into a separate chain or ensure every
+ earlier step calls `next`.
+* **Ordering matters**: handlers run in the order you register them.
+* **`When(...).ThenContinue` vs `ThenStop`**:
+
+ * `ThenContinue` executes the action and *always* calls `next`.
+ * `ThenStop` executes the action and *never* calls `next`.
+
+---
+
+## Patterns you’ll use
+
+* **Auth gate + logging** (strict stop):
+
+ ```csharp
+ var chain = ActionChain.Create()
+ .When(static (in r) => r.Path.StartsWith("/admin") &&
+ !r.Headers.ContainsKey("Authorization"))
+ .ThenStop(r => audit.Deny(r)) // stop: no tail
+ .Finally((in r, next) => { audit.Seen(r); next(in r); })
+ .Build();
+ ```
+
+* **Pre-authorization checks** (multiple early exits):
+
+ ```csharp
+ var chain = ActionChain.Create()
+ .When(static (in c) => c.Items.Count == 0)
+ .ThenStop(c => c.Fail("empty-basket"))
+ .When(static (in c) => c.CustomerAge < 21 && c.Items.Any(i => i.AgeRestricted))
+ .ThenStop(c => c.Fail("age"))
+ .Finally((in c, next) => { c.Pass("preauth-ok"); next(in c); })
+ .Build();
+ ```
+
+* **Branchless rule packs** (totals/discounts):
+
+ ```csharp
+ var totals = ActionChain.Create()
+ .Use(static (in c, next) => { c.RecomputeSubtotal(); next(in c); })
+ .When(static (in c) => c.FirstTenderIsCash).ThenContinue(c => c.AddDiscount(0.02m, "cash"))
+ .When(static (in c) => c.HasLoyalty).ThenContinue(c => c.AddDiscount(0.05m, "loyalty"))
+ .Finally(static (in c, next) => { c.ComputeTax(); next(in c); })
+ .Build();
+ ```
+
+---
+
+## Testing with TinyBDD (spec-style)
+
+```csharp
+await Given("a chain that denies /admin without auth", () =>
+{
+ var log = new List();
+ var chain = ActionChain.Create()
+ .When((in r) => r.Path.StartsWith("/admin") &&
+ !r.Headers.ContainsKey("Authorization"))
+ .ThenStop(r => log.Add("deny"))
+ .Finally((in r, next) => { log.Add($"{r.Method} {r.Path}"); next(in r); })
+ .Build();
+ return (chain, log);
+})
+.When("GET /admin no auth", s => { s.chain.Execute(new("GET","/admin",new Dictionary())); return s; })
+.Then("first line is deny", s => s.log[0] == "deny")
+.And("no tail logged (strict stop)", s => s.log.Count == 1)
+.AssertPassed();
+```
+
+---
+
+## Tips & gotchas
+
+* **Want tail to always run?** Don’t use `ThenStop` earlier, or move the “always-run” logic to a separate chain invoked
+ after this one.
+* **Avoid captures**: copy locals (`var pred = _pred;`) like the builder does, to keep delegates non-allocating.
+* **Use `in` everywhere**: it keeps hot-path costs low for structs and larger contexts.
+* **Compose freely**: you can wrap chains into stages (e.g., a transaction pipeline), or put chains behind higher-level
+ builders.
+
+---
+
+## See also
+
+* [ResultChain](./resultchain.md) – like ActionChain, but steps return a result and short-circuit on failure.
+* [BranchBuilder](../../creational/builder/branchbuilder.md) – zero-`if` router (predicate → step) for first-match-wins dispatch.
+* [Strategy](../strategy/strategy.md) / [TryStrategy](../strategy/trystrategy.md) – single-choice or first-success selection for handlers/parsers.
diff --git a/docs/patterns/behavioral/chain/resultchain.md b/docs/patterns/behavioral/chain/resultchain.md
new file mode 100644
index 0000000..e203153
--- /dev/null
+++ b/docs/patterns/behavioral/chain/resultchain.md
@@ -0,0 +1,172 @@
+# Result Chain (`Behavioral.Chain.ResultChain`)
+
+A **first–match-wins** chain that can *produce a value*.
+Each handler receives `(in TIn input, out TOut? result, Next next)` and can either:
+
+* **Produce** a result and return `true` → the chain short-circuits, or
+* **Delegate** to `next(input, out result)` → the chain continues.
+
+It’s the “router with a return value” sibling of [`ActionChain`](./actionchain.md).
+
+---
+
+## Why use `ResultChain`?
+
+Use it when you need **ordered rules that return something**:
+
+* HTTP‐ish routing → `HttpResponse`
+* Command parsers → `ICommand`
+* Promotion/price/feature selection → `CalculationResult`
+* Fallback/“NotFound” defaults via a terminal tail
+
+If you only need **side effects**, prefer `ActionChain`.
+If you want a simpler **first match mapping** (no `next`), see `Strategy`/`TryStrategy`.
+
+---
+
+## Quick example: tiny router
+
+```csharp
+using PatternKit.Behavioral.Chain;
+
+public readonly record struct Request(string Method, string Path);
+public readonly record struct Response(int Status, string Body);
+
+var router = ResultChain.Create()
+ // GET /health
+ .When(static (in r) => r.Method == "GET" && r.Path == "/health")
+ .Then(r => new Response(200, "OK"))
+ // GET /users/{id}
+ .When(static (in r) => r.Method == "GET" && r.Path.StartsWith("/users/"))
+ .Then(r => new Response(200, $"user:{r.Path[7..]}"))
+ // default / not found
+ .Finally(static (in _, out Response? res, _) => { res = new(404, "not found"); return true; })
+ .Build();
+
+var ok1 = router.Execute(in new Request("GET", "/health"), out var res1); // ok1=true, res1=200 OK
+var ok2 = router.Execute(in new Request("GET", "/nope"), out var res2); // ok2=true, res2=404
+```
+
+### “Sometimes produce, sometimes delegate”
+
+Use `When(...).Do(handler)` when a rule might *conditionally* produce and otherwise pass control onward:
+
+```csharp
+var chain = ResultChain.Create()
+ .When(static (in x) => x % 2 == 0).Do(static (in x, out string? r, ResultChain.Next next) =>
+ {
+ // Only handle THE answer; otherwise delegate
+ if (x == 42) { r = "forty-two"; return true; }
+ return next(in x, out r);
+ })
+ .When(static (in x) => x % 2 == 0).Then(_ => "even") // handles delegated evens
+ .Finally(static (in _, out string? r, _) => { r = "odd"; return true; })
+ .Build();
+```
+
+---
+
+## API surface
+
+```csharp
+public sealed class ResultChain
+{
+ public delegate bool Next(in TIn input, out TOut? result);
+ public delegate bool TryHandler(in TIn input, out TOut? result, Next next);
+ public delegate bool Predicate(in TIn input);
+
+ public bool Execute(in TIn input, out TOut? result);
+
+ public sealed class Builder
+ {
+ public Builder Use(TryHandler handler);
+ public WhenBuilder When(Predicate predicate);
+ public Builder Finally(TryHandler tail); // terminal fallback
+ public ResultChain Build();
+
+ public sealed class WhenBuilder
+ {
+ public Builder Do(TryHandler handler); // may produce OR delegate
+ public Builder Then(Func produce); // produces and stops
+ }
+ }
+
+ public static Builder Create();
+}
+```
+
+### Semantics
+
+* **Order is preserved.** First matching producer wins.
+* **`Then(Func)`**: if predicate is true → produce result, return `true`.
+* **`Do(TryHandler)`**: you decide to produce or delegate by calling `next`.
+* **`Finally(TryHandler)`**: runs *only* if the chain reaches the tail (i.e., nobody produced earlier).
+ Typical use: default/NotFound.
+* **`Execute(...)`** returns:
+
+ * `true` when any handler (or `Finally`) produced a result; `result` is set.
+ * `false` when no one produced and no `Finally` ran; `result` is `default`.
+
+---
+
+## Patterns
+
+### 1) Default/NotFound tail
+
+```csharp
+.Finally(static (in _, out MyResult? r, _) => { r = MyResult.NotFound; return true; })
+```
+
+### 2) Multi-stage processing
+
+Chain “can I handle this?” steps. Each step may produce, or else delegate:
+
+```csharp
+.When(static (in x) => IsFastPath(x)).Do(static (in x, out R? r, var next) =>
+{
+ if (TryFast(x, out r)) return true;
+ return next(in x, out r); // let later handlers try
+})
+.When(static (in x) => IsSlowPath(x)).Then(SlowCompute)
+```
+
+### 3) Cross-cut logging without duplication
+
+Put it in `Finally` **only if** you want it to run when nothing matched.
+Otherwise log inside the `Then/Do` that produced.
+
+---
+
+## Performance & threading
+
+* The chain composes to a **single delegate** at `Build()` time (reverse fold).
+ No allocations during `Execute` besides what your handlers do.
+* The built chain is **immutable** and **thread-safe**. Builders are **not** thread-safe.
+
+---
+
+## Gotchas & tips
+
+* For lambdas passed to `When(...)`, the parameter is **`in`**.
+ Prefer explicit static lambdas to avoid captures and compiler warnings:
+
+ ```csharp
+ .When(static (in r) => r.Flag) // good
+ .Then(static r => ...) // Then’s lambda is a normal parameter (no `in`)
+ ```
+
+* If you omit `Finally` and nothing produces, `Execute` returns `false` and `result` is `default`.
+
+---
+
+## Tests
+
+See `PatternKit.Tests/Behavioral/Chain/ResultChainTests.cs` for executable specs covering:
+
+* First match wins; fallback via `Finally`
+* No tail → `Execute` returns `false`
+* `When.Do` → produce vs delegate
+* Registration order guarantees
+* Tail runs only when no earlier producer
+
+These tests use **TinyBDD** so the assertions read like documentation.
diff --git a/docs/patterns/behavioral/strategy/actionstrategy.md b/docs/patterns/behavioral/strategy/actionstrategy.md
new file mode 100644
index 0000000..2a56158
--- /dev/null
+++ b/docs/patterns/behavioral/strategy/actionstrategy.md
@@ -0,0 +1,153 @@
+# ActionStrategy\
+
+A tiny, **first-match-wins** strategy where each branch runs a **side-effecting action** (no return value). Think of it as the “actions-only” sibling of `Strategy` / `TryStrategy`.
+
+* **Input:** `in TIn`
+* **Match:** first predicate that returns `true`
+* **Action:** runs once (no fallthrough)
+* **Fallback:** optional default action
+* **APIs:** `Execute(in TIn)` (throws if nothing matches and no default), `TryExecute(in TIn)` (never throws; returns `true/false`)
+
+---
+
+## When to use
+
+Use `ActionStrategy` when you need exactly one of several **procedures** to run for a given input—logging, routing to an action that doesn’t produce a value, selecting a handler, etc. If you need to **compute a result**, use `Strategy` / `TryStrategy` instead.
+
+---
+
+## Quick start
+
+```csharp
+using PatternKit.Behavioral.Strategy;
+
+var log = new List();
+
+var s = ActionStrategy.Create()
+ .When(static (in i) => i > 0).Then(static (in i) => log.Add($"+{i}"))
+ .When(static (in i) => i < 0).Then(static (in i) => log.Add($"{i}"))
+ .Default(static (in _) => log.Add("zero"))
+ .Build();
+
+s.Execute(5); // logs "+5"
+s.Execute(-3); // logs "-3"
+s.Execute(0); // logs "zero"
+```
+
+### No default vs default
+
+```csharp
+var noDefault = ActionStrategy.Create()
+ .When(static (in i) => i % 2 == 0).Then(static (in i) => Console.WriteLine($"even:{i}"))
+ .Build();
+
+noDefault.TryExecute(3); // false, nothing ran
+// noDefault.Execute(3); // throws InvalidOperationException (no match, no default)
+
+var withDefault = ActionStrategy.Create()
+ .Default(static (in _) => Console.WriteLine("fallback"))
+ .Build();
+
+withDefault.TryExecute(3); // true, wrote "fallback"
+withDefault.Execute(3); // also writes "fallback"
+```
+
+---
+
+## Builder API (at a glance)
+
+```csharp
+var s = ActionStrategy.Create()
+ .When(Predicate).Then(ActionHandler) // add as many branches as you want
+ .Default(ActionHandler) // optional
+ .Build();
+```
+
+* `When(Predicate)`: start a branch (predicate signature: `bool(in TIn)`).
+* `Then(ActionHandler)`: action to run when the branch matches (`void(in TIn)`).
+* `Default(ActionHandler)`: action to run when nothing matched.
+* `Build()`: composes an immutable, thread-safe strategy.
+
+**Execution semantics**
+
+* Branches are evaluated in **registration order**.
+* The **first** matching `When(...).Then(...)` runs; no later branches are considered.
+* If nothing matched:
+
+ * `Execute` runs **default** if present; otherwise throws.
+ * `TryExecute` returns **true** if default ran; otherwise **false**.
+
+---
+
+## Ordering guarantees
+
+Registration order is preserved. Only the **first** matching action runs:
+
+```csharp
+var log = new List();
+var s = ActionStrategy.Create()
+ .When(static (in i) => i % 2 == 0).Then(static (in _) => log.Add("first"))
+ .When(static (in i) => i >= 0).Then(static (in _) => log.Add("second"))
+ .Default(static (in _) => log.Add("default"))
+ .Build();
+
+s.Execute(2);
+Console.WriteLine(string.Join("|", log)); // "first"
+```
+
+---
+
+## Error handling
+
+* `Execute(in TIn)` throws `InvalidOperationException` when **no** predicate matches **and** there is **no** default.
+* `TryExecute(in TIn)` never throws due to “no match”; it simply returns `false`.
+
+---
+
+## Performance & thread-safety
+
+* The builder compiles arrays of predicates/actions once at `Build()` time.
+* The built strategy is **immutable** and **thread-safe** (you can cache and reuse).
+* Delegates use `in` parameters to avoid defensive copies for structs.
+
+---
+
+## Testing tips (TinyBDD snippet)
+
+Your unit tests can read like specs:
+
+```csharp
+using TinyBDD;
+using TinyBDD.Xunit;
+using PatternKit.Behavioral.Strategy;
+
+public class ActionStrategySpec : TinyBddXunitBase
+{
+ [Fact]
+ public async Task first_match_and_default()
+ {
+ var log = new List();
+ var s = ActionStrategy.Create()
+ .When(static (in i) => i > 0).Then((in i) => log.Add($"+{i}"))
+ .When(static (in i) => i < 0).Then((in i) => log.Add($"{i}"))
+ .Default(static (in _) => log.Add("zero"))
+ .Build();
+
+ await Given("a composed action strategy", () => s)
+ .When("executing 5", _ => { s.Execute(5); return 0; })
+ .And("executing -3", _ => { s.Execute(-3); return 0; })
+ .And("executing 0", _ => { s.Execute(0); return 0; })
+ .Then("logs +5|-3|zero", _ => string.Join("|", log) == "+5|-3|zero")
+ .AssertPassed();
+ }
+}
+```
+
+---
+
+## Related patterns
+
+* **Produces a value?** Use \[`Strategy`] or \[`TryStrategy`].
+* **Needs side effects with short-circuiting middleware style?** See \[`ActionChain`].
+
+> All of these follow the same **first-match-wins** philosophy so you can compose small units without `if/else` tangles.
diff --git a/docs/patterns/behavioral/strategy/asyncstrategy.md b/docs/patterns/behavioral/strategy/asyncstrategy.md
new file mode 100644
index 0000000..e5716c8
--- /dev/null
+++ b/docs/patterns/behavioral/strategy/asyncstrategy.md
@@ -0,0 +1,162 @@
+# AsyncStrategy\
+
+A **first-match-wins, asynchronous strategy**: evaluate predicates in order and execute the handler for the **first**
+branch that matches. Handlers return a `TOut` via `ValueTask`.
+
+Great for things like: async routing/dispatch, picking a storage backend, selecting an algorithm or serializer, or any
+“try A, else B” flow that needs awaits.
+
+---
+
+## Why use it
+
+* **Deterministic control flow**: registration order = evaluation order; only the first match runs.
+* **Async-first**: both predicates and handlers can await.
+* **Explicit defaults**: optional fallback handler when nothing matches.
+* **Immutable & thread-safe** after `Build()` (safe for concurrent calls).
+* **Allocation-light**: uses arrays and `ValueTask` to minimize overhead.
+
+---
+
+## TL;DR example
+
+```csharp
+using PatternKit.Behavioral.Strategy;
+
+var strat = AsyncStrategy.Create()
+ .When((n, ct) => new ValueTask(n < 0))
+ .Then((n, ct) => new ValueTask("negative"))
+ .When((n, ct) => new ValueTask(n == 0))
+ .Then((n, ct) => new ValueTask("zero"))
+ .Default((n, ct) => new ValueTask("positive"))
+ .Build();
+
+var s1 = await strat.ExecuteAsync(-5); // "negative"
+var s2 = await strat.ExecuteAsync(0); // "zero"
+var s3 = await strat.ExecuteAsync(7); // "positive"
+```
+
+---
+
+## Building branches
+
+Each branch is a **predicate + handler** pair:
+
+```csharp
+var s = AsyncStrategy.Create()
+ .When((req, ct) => new ValueTask(req.Path.StartsWith("/admin")))
+ .Then(async (req, ct) =>
+ {
+ await Audit(req, ct);
+ return await HandleAdmin(req, ct);
+ })
+ .When((req, ct) => new ValueTask(req.Path.StartsWith("/api/")))
+ .Then((req, ct) => HandleApi(req, ct)) // already returns ValueTask
+ .Default((req, ct) => NotFound(req)) // fallback runs if nothing matched
+ .Build();
+```
+
+**First match wins**: if multiple predicates are `true`, only the earliest registered one runs.
+
+---
+
+## Defaults & errors
+
+* If you provide **`.Default(handler)`**, it runs when no predicates match.
+* If you **omit** a default and nothing matches, `ExecuteAsync` throws `InvalidOperationException`
+ to signal **no branch matched**.
+
+---
+
+## Cancellation
+
+`CancellationToken` flows into both predicate and handler:
+
+```csharp
+var s = AsyncStrategy.Create()
+ .When((_, ct) =>
+ {
+ ct.ThrowIfCancellationRequested();
+ return new ValueTask(true);
+ })
+ .Then((_, ct) =>
+ {
+ ct.ThrowIfCancellationRequested();
+ return new ValueTask("ok");
+ })
+ .Build();
+```
+
+If the token is canceled, your predicate/handler can throw `OperationCanceledException` and the call will surface it.
+
+---
+
+## Synchronous adapters (nice for quick wiring)
+
+You don’t have to write `ValueTask` everywhere:
+
+```csharp
+var s = AsyncStrategy.Create()
+ .When(n => n % 2 == 0) // sync predicate
+ .Then((_, _) => new ValueTask("even"))
+ .When((n, _) => new ValueTask(n >= 0))
+ .Then((_, _) => new ValueTask("nonneg"))
+ .Default(_ => "other") // sync default
+ .Build();
+
+await s.ExecuteAsync(2); // "even"
+await s.ExecuteAsync(1); // "nonneg"
+await s.ExecuteAsync(-1); // "other"
+```
+
+---
+
+## Testing (TinyBDD style)
+
+```csharp
+[Scenario("First matching async branch runs; default used when none match")]
+[Fact]
+public async Task FirstMatchAndDefault()
+{
+ var log = new List();
+ var strat = AsyncStrategy.Create()
+ .When((n, _) => new ValueTask(n > 0))
+ .Then((n, _) => { log.Add("pos"); return new ValueTask("+" + n); })
+ .When((n, _) => new ValueTask(n < 0))
+ .Then((n, _) => { log.Add("neg"); return new ValueTask(n.ToString()); })
+ .Default((_, _) => new ValueTask("zero"))
+ .Build();
+
+ var r1 = await strat.ExecuteAsync(5);
+ Assert.Equal("+5", r1);
+ Assert.Equal("pos", string.Join("|", log));
+}
+```
+
+Our repository includes comprehensive tests covering: first-match behavior, default vs. throw, sync adapters, order
+guarantees, and cancellation.
+
+---
+
+## Design notes
+
+* **Built on `BranchBuilder`**: the builder collects `(Predicate, Handler)` pairs and compiles them into arrays.
+* **`ValueTask` everywhere**: avoids allocating `Task` for already-completed operations.
+* **No reflection / no LINQ** in the hot path: simple loops over arrays.
+
+---
+
+## Gotchas
+
+* **Order matters.** Put the most specific predicates first.
+* **Default is optional.** Without it, expect `InvalidOperationException` when nothing matches.
+* **Predicate/handler exceptions** are not swallowed—let them surface or handle them upstream.
+
+---
+
+## See also
+
+* [ActionStrategy](./actionstrategy.md) — first-match actions with no return value.
+* [Strategy](./strategy.md) — synchronous result-producing strategy (throws on no match).
+* [TryStrategy](./trystrategy.md) — synchronous, result-producing strategy that can “not match” without throwing.
+* [BranchBuilder](../../creational/builder/branchbuilder.md) — the generic composition utility AsyncStrategy builds upon.
diff --git a/docs/patterns/behavioral/strategy/strategy.md b/docs/patterns/behavioral/strategy/strategy.md
new file mode 100644
index 0000000..984d873
--- /dev/null
+++ b/docs/patterns/behavioral/strategy/strategy.md
@@ -0,0 +1,159 @@
+# Strategy\
+
+A **first-match-wins, synchronous strategy**: evaluate predicates in order and execute the handler for the **first** branch that matches. The chosen handler returns a `TOut`.
+
+Use it to replace `switch`/`if-else` cascades with a small, composable decision pipeline: routing, labelers, mappers, pick-an-algorithm, etc.
+
+---
+
+## What it is
+
+* **Deterministic branching**: registration order = evaluation order; only the first match runs.
+* **Return a value**: each handler produces a `TOut`.
+* **Optional default**: a fallback handler when nothing matches; otherwise `Execute` throws.
+* **Immutable & thread-safe** after `Build()`.
+
+> If you want a non-throwing variant, see **TryStrategy\**.
+> If you only need side effects (no return), see **ActionStrategy\**.
+
+---
+
+## TL;DR example
+
+```csharp
+using PatternKit.Behavioral.Strategy;
+
+var classify = Strategy.Create()
+ .When(static i => i > 0).Then(static _ => "positive")
+ .When(static i => i < 0).Then(static _ => "negative")
+ .Default(static _ => "zero")
+ .Build();
+
+var a = classify.Execute( 7); // "positive"
+var b = classify.Execute(-3); // "negative"
+var c = classify.Execute( 0); // "zero"
+```
+
+If you **omit** `.Default(...)` and nothing matches, `Execute` throws `InvalidOperationException` (via `Throw.NoStrategyMatched()`).
+
+---
+
+## Building branches
+
+Each branch is a **predicate + handler** pair:
+
+```csharp
+var chooseStorage = Strategy.Create()
+ .When(static path => path.StartsWith("s3:", StringComparison.Ordinal))
+ .Then(static _ => new S3BlobStore())
+ .When(static path => path.StartsWith("gs:", StringComparison.Ordinal))
+ .Then(static _ => new GcsBlobStore())
+ .Default(static _ => new FileSystemBlobStore())
+ .Build();
+```
+
+**First match wins.** If more than one predicate is `true`, only the earliest one runs.
+
+---
+
+## Typical patterns
+
+### 1) Simple mapping / labeling
+
+```csharp
+var label = Strategy.Create()
+ .When(static n => (n & 1) == 0).Then(static _ => "even")
+ .When(static n => n % 3 == 0).Then(static _ => "div3")
+ .Default(static _ => "other")
+ .Build();
+```
+
+### 2) Content negotiation (sync)
+
+```csharp
+var pickWriter = Strategy.Create()
+ .When(static ct => ct == "application/json").Then(static _ => new JsonWriter())
+ .When(static ct => ct == "text/csv").Then(static _ => new CsvWriter())
+ .Default(static _ => new TextWriter())
+ .Build();
+```
+
+### 3) Rule packs with “most specific first”
+
+```csharp
+var price = Strategy- .Create()
+ .When(static it => it.OnSale).Then(static it => it.BasePrice * 0.8m)
+ .When(static it => it.IsWholesale).Then(static it => it.BasePrice * 0.9m)
+ .Default(static it => it.BasePrice)
+ .Build();
+```
+
+---
+
+## API shape
+
+```csharp
+var s = Strategy.Create()
+ .When(static (in TIn x) => /* bool */).Then(static (in TIn x) => /* TOut */)
+ .Default(static (in TIn x) => /* TOut */) // optional
+ .Build();
+
+TOut result = s.Execute(in input); // throws if no match and no default
+```
+
+* **`When(predicate).Then(handler)`**: registers a branch.
+* **`.Default(handler)`**: sets the fallback when no predicates match.
+* **`Execute(in TIn)`**: runs the first matching handler or the default; throws if neither exists.
+
+All delegates accept `in TIn` for zero-copy pass-through of structs.
+
+---
+
+## Testing (TinyBDD style)
+
+```csharp
+[Scenario("First-match wins; default runs when none match")]
+[Fact]
+public async Task Strategy_FirstMatch_Default()
+{
+ var strat = Strategy.Create()
+ .When(static i => i > 0).Then(static _ => "pos")
+ .When(static i => i < 0).Then(static _ => "neg")
+ .Default(static _ => "zero")
+ .Build();
+
+ await Given("the strategy", () => strat)
+ .When("Execute(3)", s => s.Execute(3))
+ .Then("is 'pos'", r => r == "pos")
+ .When("Execute(-2)", s => strat.Execute(-2))
+ .Then("is 'neg'", r => r == "neg")
+ .When("Execute(0)", s => strat.Execute(0))
+ .Then("is 'zero'", r => r == "zero")
+ .AssertPassed();
+}
+```
+
+---
+
+## Design notes
+
+* **No LINQ / reflection in the hot path** — predicates/handlers are arrays iterated with a simple `for` loop.
+* **Immutability** — after `Build()` the strategy can be shared across threads.
+* **Order matters** — put the most specific predicates first.
+
+---
+
+## Gotchas
+
+* **No default + no match ⇒ throw.** Use **TryStrategy\** if you want a non-throwing “no match” path (`bool Execute(in, out TOut?)`).
+* **Side effects?** Prefer **ActionStrategy\** when you only need actions and no return value.
+* **Async?** Use **AsyncStrategy\** when your predicates/handlers await.
+
+---
+
+## See also
+
+* [TryStrategy](./trystrategy.md) — first-match with `bool` success + `out` result (no throw on no match).
+* [ActionStrategy](./actionstrategy.md) — first-match, side-effect only (no result).
+* [AsyncStrategy](./asyncstrategy.md) — first-match with async handlers returning `ValueTask`.
+* [BranchBuilder](../../creational/builder/branchbuilder.md) — the low-level composer used by all strategies.
diff --git a/docs/patterns/behavioral/strategy/trystrategy.md b/docs/patterns/behavioral/strategy/trystrategy.md
new file mode 100644
index 0000000..395c753
--- /dev/null
+++ b/docs/patterns/behavioral/strategy/trystrategy.md
@@ -0,0 +1,69 @@
+# TryStrategy
+
+A **first-success, non-throwing strategy**: evaluate a sequence of `Try` handlers in order until one succeeds. Each handler attempts to produce a `TOut` and returns `true` on success (setting the `out` value) or `false` to let the next handler try.
+
+Use `TryStrategy` when you want a safe, allocation-light parsing/coercion pipeline (e.g., `Coercer`) or any "try A, else B" flow where failures are expected and should not throw.
+
+---
+
+## TL;DR
+
+```csharp
+var parser = TryStrategy.Create()
+ .Always((in string s, out int r) => int.TryParse(s, out r))
+ .Finally((in string _, out int r) => { r = 0; return true; })
+ .Build();
+
+if (parser.Execute("123", out var n)) Console.WriteLine(n); // 123
+```
+
+`Execute(in, out)` returns `true` when a handler produced a value; otherwise `false` and `out` is `default` (unless the optional `Finally` provided a fallback).
+
+---
+
+## What it is
+
+* **First-success wins**: handlers are tried in registration order; the first that returns `true` wins.
+* **Non-throwing**: `Execute` signals success via a `bool`, never throws just because no handler matched.
+* **Low-cost hot path**: handlers are compiled into arrays and iterated in a simple `for` loop.
+
+---
+
+## API shape
+
+```csharp
+var b = TryStrategy.Create()
+ .Always(TryHandler) // append a handler that may succeed
+ .Finally(TryHandler) // optional fallback (always runs if provided)
+ .Build();
+
+bool ok = b.Execute(in input, out TOut? result);
+```
+
+* `Always(TryHandler)` (or `.When(...).ThenTry(...)` in some builders) registers attempts.
+* `Finally(TryHandler)` provides a guaranteed fallback; callers can still use the boolean result to detect whether a "real" handler succeeded.
+
+---
+
+## Typical patterns
+
+* **Coercion / parsing**: chain a set of type-specific parsers, ending with a convertible fallback. See `Coercer`.
+* **Content negotiation**: try several negotiators until one reports `true` and sets a content type.
+* **Loose deserialization**: try JSON, then CSV, then plain string parsing.
+
+---
+
+## Gotchas
+
+* Handlers must not swallow exceptions you want surfaced; if a handler throws, the exception will propagate unless you explicitly catch inside the handler.
+* Registration order matters: put the fastest and most-specific handlers first.
+
+---
+
+## See also
+
+* [Strategy](./strategy.md) — first-match that returns a `TOut` and throws when nothing matches.
+* [ActionStrategy](./actionstrategy.md) — first-match actions with no return value.
+* [AsyncStrategy](./asyncstrategy.md) — async first-match strategy.
+* [BranchBuilder](../../creational/builder/branchbuilder.md) — the low-level composer used by strategy families.
+
diff --git a/docs/patterns/creational/builder/branchbuilder.md b/docs/patterns/creational/builder/branchbuilder.md
new file mode 100644
index 0000000..cd1fb3e
--- /dev/null
+++ b/docs/patterns/creational/builder/branchbuilder.md
@@ -0,0 +1,152 @@
+# BranchBuilder\
+
+A tiny, reusable builder for collecting **predicate/handler pairs** (plus an optional **default**) and projecting them into any concrete “strategy-like” product. It’s the core used by `ActionStrategy`, `Strategy`, and `AsyncStrategy`.
+
+---
+
+## Why it exists
+
+Lots of “first-match-wins” constructs look the same: you register ordered predicate/handler pairs, optionally set a default, then build an immutable thing. `BranchBuilder` captures that pattern so you can:
+
+* Avoid re-implementing the same plumbing.
+* Keep allocations minimal (lists while building, single `ToArray()` on `Build()`).
+* Project the collected data into any product type via a **projector** function.
+
+---
+
+## Mental model
+
+* **Registration order matters.** The `i`th predicate corresponds to the `i`th handler.
+* **Default is optional.** If you don’t set one, a **fallback** you supply at build time is used, and you get a `hasDefault=false` flag.
+* **Build is a snapshot.** Each call copies to arrays; later calls don’t mutate earlier products.
+
+---
+
+## API at a glance
+
+```csharp
+var b = BranchBuilder.Create();
+
+b.Add(TPred predicate, THandler handler); // append a pair (order preserved)
+b.Default(THandler handler); // set/replace default
+
+TProduct product = b.Build(
+ fallbackDefault: THandler, // used if no Default() was configured
+ projector: (TPred[] preds,
+ THandler[] handlers,
+ bool hasDefault,
+ THandler @default) => /* construct product */
+);
+```
+
+### Threading & immutability
+
+* Builders are **not** thread-safe.
+* Arrays passed to your projector are **fresh snapshots**. Treat them as immutable in your product.
+
+---
+
+## Minimal examples
+
+### 1) Build a simple classifier (sync)
+
+```csharp
+// Shapes
+delegate bool Pred(in int x);
+delegate string Handler(in int x);
+
+// Predicates/handlers
+static bool IsEven(in int x) => (x & 1) == 0;
+static bool IsPositive(in int x) => x > 0;
+static string HandleEven(in int _) => "even";
+static string HandlePositive(in int _) => "pos";
+static string Fallback(in int _) => "other";
+
+sealed record Classifier(Pred[] Preds, Handler[] Handlers, bool HasDefault, Handler Default)
+{
+ public string Execute(in int x)
+ {
+ for (var i = 0; i < Preds.Length; i++)
+ if (Preds[i](in x)) return Handlers[i](in x);
+ return Default(in x);
+ }
+}
+
+var classifier =
+ BranchBuilder.Create()
+ .Add(IsEven, HandleEven)
+ .Add(IsPositive, HandlePositive)
+ .Build(fallbackDefault: Fallback,
+ projector: (p, h, hasDef, def) => new Classifier(p, h, hasDef, def));
+
+classifier.Execute(2); // "even"
+classifier.Execute(1); // "pos"
+classifier.Execute(-1); // "other" (fallback)
+```
+
+### 2) Swap in a real default (not fallback)
+
+```csharp
+static string RealDefault(in int _) => "default";
+
+var withRealDefault =
+ BranchBuilder.Create()
+ .Add(IsEven, HandleEven)
+ .Default(RealDefault)
+ .Build(Fallback, (p, h, hasDef, def) => new Classifier(p, h, hasDef, def));
+
+// withRealDefault.HasDefault == true; withRealDefault.Default == RealDefault
+```
+
+### 3) What the built-in strategies do
+
+All of these are thin wrappers over `BranchBuilder`:
+
+* `ActionStrategy` → predicates + **action** handlers (`void`).
+* `Strategy` → predicates + **result** handlers (`TOut`).
+* `AsyncStrategy` → async predicates/handlers (`ValueTask`).
+
+Each supplies a sensible **fallback default** to `Build(...)` and a projector that constructs the immutable strategy.
+
+---
+
+## Usage patterns & tips
+
+* **Replace defaults:** calling `Default(...)` multiple times replaces the previous one (“last wins”).
+* **Conditional registration:** gate calls to `.Add(...)` with your own `if` or feature flags. (The conditional DSL lives in `TryStrategy`; `BranchBuilder` stays simple.)
+* **Multiple products from one builder:** you can call `Build(...)` more than once. Each build snapshots current pairs and default.
+* **Interop with `in` parameters:** Using `in` in your delegate shapes keeps handlers low-overhead for structs.
+
+---
+
+## Gotchas
+
+* **No validation of shapes.** `TPred`/`THandler` are just types; ensure they’re the right delegates for your projector.
+* **Default semantics:** If you never call `Default(...)`, your projector receives `hasDefault=false` and the **fallback** handler as `@default`. Use the flag to distinguish “user configured” vs “library fallback”.
+
+---
+
+## Reference (public API)
+
+```csharp
+public sealed class BranchBuilder
+{
+ public static BranchBuilder Create();
+
+ public BranchBuilder Add(TPred predicate, THandler handler);
+ public BranchBuilder Default(THandler handler);
+
+ public TProduct Build(
+ THandler fallbackDefault,
+ Func projector);
+}
+```
+
+---
+
+## See also
+
+* [ActionStrategy](../../behavioral/strategy/actionstrategy.md) – first-match actions.
+* [Strategy](../../behavioral/strategy/strategy.md) – first-match handlers that return values.
+* [AsyncStrategy](../../behavioral/strategy/asyncstrategy.md) – async first-match strategy.
+* [ActionChain](../../behavioral/chain/actionchain.md) / [ResultChain](../../behavioral/chain/resultchain.md) – chain style (middleware) alternatives.
diff --git a/docs/patterns/creational/builder/chainbuilder.md b/docs/patterns/creational/builder/chainbuilder.md
new file mode 100644
index 0000000..dc841e5
--- /dev/null
+++ b/docs/patterns/creational/builder/chainbuilder.md
@@ -0,0 +1,120 @@
+# ChainBuilder\
+
+A tiny, allocation-light builder that collects items **in order** and then **projects** them into any product type. It’s the backbone for “append things, then freeze into an immutable structure” scenarios (e.g., composing pipelines).
+
+---
+
+## Mental model
+
+* **Append-only order.** `Add` pushes to the end; order is preserved.
+* **Conditional append.** `AddIf(cond, item)` only appends when `cond` is `true`.
+* **Snapshot on build.** `Build(projector)` copies items to a fresh array and passes it to your projector. Subsequent `Add` calls don’t mutate previously built products.
+
+---
+
+## API at a glance
+
+```csharp
+var b = ChainBuilder.Create();
+
+b.Add(T item); // append item
+b.AddIf(bool condition, T); // append only when condition is true
+
+TProduct product = b.Build(items => /* construct product from T[] */);
+```
+
+### Threading & immutability
+
+* Builders are **not** thread-safe.
+* `Build` hands you a **new array snapshot** each time—treat it as immutable in your product.
+
+---
+
+## Minimal examples
+
+### 1) Make a simple CSV projector
+
+```csharp
+var csv = ChainBuilder.Create()
+ .Add(1)
+ .Add(2)
+ .AddIf(false, 99) // ignored
+ .Build(items => string.Join(",", items));
+// "1,2"
+```
+
+### 2) Build and reuse with snapshots
+
+```csharp
+var b = ChainBuilder.Create().Add(1).Add(2);
+
+var first = b.Build(items => items.Length); // 2
+b.Add(3);
+var second = b.Build(items => items.Length); // 3
+
+// 'first' used the earlier snapshot; wasn't mutated by Add(3)
+```
+
+### 3) Compose a middleware delegate (handlers list → single runner)
+
+```csharp
+// Handler is (in TCtx ctx, Next next) => void
+public delegate void Handler(in TCtx ctx, Action next);
+
+var handlers = ChainBuilder>.Create()
+ .Add((in int x, next) => { Console.Write("[A]"); next(in x); })
+ .Add((in int x, next) => { Console.Write("[B]"); next(in x); })
+ .Build(items =>
+ {
+ // compose from end to start
+ Action next = static (in _) => { };
+ for (var i = items.Length - 1; i >= 0; i--)
+ {
+ var h = items[i];
+ var prev = next;
+ next = (in int c) => h(in c, prev);
+ }
+ return next;
+ });
+
+handlers(in 0); // prints [A][B]
+```
+
+---
+
+## Usage patterns & tips
+
+* **Feature-flagged registration:** wrap `AddIf(flag, item)` to keep builder clutter-free.
+* **Multiple products from one builder:** call `Build` multiple times with different projectors (e.g., build a runner and a debug view).
+* **Low overhead:** Lists while building; exactly one `ToArray()` per `Build`.
+
+---
+
+## Gotchas
+
+* **No removal/reorder.** It’s purposefully simple—append in the order you want to execute.
+* **Projector owns semantics.** `ChainBuilder` doesn’t interpret items; your projector decides what they mean.
+
+---
+
+## Reference (public API)
+
+```csharp
+public sealed class ChainBuilder
+{
+ public static ChainBuilder Create();
+
+ public ChainBuilder Add(T item);
+ public ChainBuilder AddIf(bool condition, T item);
+
+ public TProduct Build(Func projector);
+}
+```
+
+---
+
+## See also
+
+* [BranchBuilder](./branchbuilder.md) – collect predicate/handler pairs + optional default, then project.
+* [Behavioral.Chain.ActionChain](../../behavioral/chain/actionchain.md) / [Behavioral.Chain.ResultChain](../../behavioral/chain/resultchain.md) – real pipelines built atop these patterns.
+* [Behavioral.Strategy.TryStrategy](../../behavioral/strategy/trystrategy.md) – uses `ChainBuilder` for first-success execution.
diff --git a/docs/patterns/creational/builder/composer.md b/docs/patterns/creational/builder/composer.md
new file mode 100644
index 0000000..6c47234
--- /dev/null
+++ b/docs/patterns/creational/builder/composer.md
@@ -0,0 +1,153 @@
+# Composer\
+
+A tiny, explicit **functional** builder: you accumulate immutable state (usually a small struct) via **pure transformations**, optionally add **validations**, then **project** the final state into your output type.
+
+---
+
+## Mental model
+
+* **Seed → Transform → Validate → Project.**
+* `With` composes functions **left-to-right** (i.e., `b(a(seed))`).
+* `Require` chains validators; the **first non-null message** throws.
+* Nothing happens until `Build` — that’s when transforms and validators run.
+
+---
+
+## API at a glance
+
+```csharp
+// Create with a seed factory (prefer static to avoid captures)
+var c = Composer.New(static () => default);
+
+// Add transforms (pure functions State -> State)
+c.With(static s => /* change s */);
+
+// Add validators (State -> string?); return null when OK
+c.Require(static s => /* message-or-null */);
+
+// Finish: transform final State into your output type
+Dto dto = c.Build(static s => new Dto(/* from s */));
+```
+
+### Threading & immutability
+
+* The composer instance is mutable **until** `Build`. You can keep calling `With`/`Require` and `Build` repeatedly.
+* The *state* you produce should be treated as immutable; prefer small `record struct`s for perf.
+
+---
+
+## Minimal examples
+
+### 1) Basic composition
+
+```csharp
+public readonly record struct PersonState(string? Name, int Age);
+public sealed record PersonDto(string Name, int Age);
+
+var dto = Composer
+ .New(static () => default) // (Name=null, Age=0)
+ .With(static s => s with { Name = "Ada" })
+ .With(static s => s with { Age = 30 })
+ .Require(static s => string.IsNullOrWhiteSpace(s.Name) ? "Name is required." : null)
+ .Build(static s => new PersonDto(s.Name!, s.Age));
+// -> PersonDto("Ada", 30)
+```
+
+### 2) Left-to-right transform order
+
+```csharp
+static PersonState A(PersonState s) => s with { Age = 10 };
+static PersonState B(PersonState s) => s with { Age = 20 };
+
+var dto = Composer
+ .New(static () => default)
+ .With(A) // sets Age to 10
+ .With(B) // then overrides to 20
+ .Require(static _ => null)
+ .Build(static s => new PersonDto(s.Name ?? "?", s.Age));
+// Age == 20
+```
+
+### 3) Multiple validators (first failure wins)
+
+```csharp
+static string? NameRequired(PersonState s)
+ => string.IsNullOrWhiteSpace(s.Name) ? "Name is required." : null;
+
+static string? AgeInRange(PersonState s)
+ => s.Age is < 0 or > 130 ? $"Age must be within [0..130] but was {s.Age}." : null;
+
+var ex = Assert.Throws(() =>
+ Composer.New(static () => new(null, -5))
+ .Require(NameRequired) // fails first -> throws this message
+ .Require(AgeInRange)
+ .Build(static s => new PersonDto(s.Name!, s.Age)));
+Assert.Equal("Name is required.", ex.Message);
+```
+
+### 4) Reuse a composer
+
+```csharp
+var comp = Composer
+ .New(static () => default)
+ .With(static s => s with { Name = "Ada" })
+ .Require(static _ => null);
+
+var dto1 = comp.Build(static s => new PersonDto(s.Name!, s.Age)); // ("Ada", 0)
+var dto2 = comp.With(static s => s with { Age = 30 })
+ .Build(static s => new PersonDto(s.Name!, s.Age)); // ("Ada", 30)
+```
+
+---
+
+## Patterns & tips
+
+* **Prefer method pointers** over capturing lambdas for AOT/JIT friendliness:
+
+ ```csharp
+ static PersonState SetName(PersonState s, string n) => s with { Name = n };
+ c.With(static s => SetName(s, "Ada"));
+ ```
+* **Validation as composition**: chain small rules with `Require`; return `null` on success.
+* **One projection, many outputs**: You can build multiple outputs by calling `Build` with different projectors.
+* **No side effects in transforms**: keep `With` pure (deterministic, no I/O) for easy reasoning and testing.
+
+---
+
+## Error handling
+
+* `Build` throws `InvalidOperationException` with the **first** validation message that is not `null`/empty.
+* If there are no validators, `Build` always succeeds.
+
+---
+
+## Performance notes
+
+* `With` composes delegates; composition cost is O(#With) executed once per `Build`.
+* Use small, shallow `record struct` state to minimize copying.
+* Prefer `static` lambdas / method groups to avoid allocations from captures.
+
+---
+
+## Reference (public API)
+
+```csharp
+public sealed class Composer
+{
+ public static Composer New(Func seed);
+
+ public Composer With(Func transform);
+
+ public Composer Require(Func validate);
+
+ public TOut Build(Func project);
+}
+```
+
+---
+
+## See also
+
+* [ChainBuilder](./chainbuilder.md) – collect items, project to a product.
+* [BranchBuilder](./branchbuilder.md) – collect predicate/handler pairs + optional default.
+* [Strategy](../../behavioral/strategy/strategy.md) / [TryStrategy](../../behavioral/strategy/trystrategy.md) / [AsyncStrategy](../../behavioral/strategy/asyncstrategy.md) – consumers of these creational patterns.
diff --git a/docs/patterns/creational/builder/mutablebuilder.md b/docs/patterns/creational/builder/mutablebuilder.md
new file mode 100644
index 0000000..f87570d
--- /dev/null
+++ b/docs/patterns/creational/builder/mutablebuilder.md
@@ -0,0 +1,98 @@
+# MutableBuilder\
+
+A small, allocation-light builder for creating and configuring mutable instances. Use `MutableBuilder` when you want to compose a sequence of in-place mutations and validations against instances produced by a factory, then produce the configured instance with a single `Build()` call.
+
+File: `docs/patterns/creational/builder/mutablebuilder.md`
+
+## TL;DR
+
+```csharp
+var person = MutableBuilder
+ .New(static () => new Person())
+ .With(p => p.Name = "Ada")
+ .With(p => p.Age = 30)
+ .Require(p => p.Name is not null && p.Name != "" ? null : "Name must be non-empty.")
+ .Build();
+```
+
+## What it is
+
+MutableBuilder\ is a tiny DSL for:
+
+- collecting mutation actions (`With`),
+- collecting validators that return an optional error message (`Require`),
+- applying mutations in registration order to a fresh instance from a factory,
+- failing fast on the first validation that returns a non-`null` message.
+
+It favors explicit, reflection-free configuration and is optimized for minimal allocations. Prefer `static` lambdas to avoid captured closures.
+
+## Key semantics
+
+- Registration order is preserved: mutations are executed in the order added.
+- Validations run in the order registered during `Build()` and the first non-`null` message causes `Build()` to throw `InvalidOperationException` with that message.
+- `Build()` calls the configured factory for each build; the builder can be reused to produce multiple instances (later builds reflect additional registered mutations/validators).
+- Builders are not thread-safe.
+
+## API at a glance
+
+- `static MutableBuilder New(Func factory)` — create a builder that calls `factory()` for each `Build()`.
+- `MutableBuilder With(Action mutation)` — append an in-place mutation.
+- `MutableBuilder Require(Func validator)` — append a validator that returns `null` for success or an error message for failure.
+- `T Build()` — create an instance via the factory, apply mutations, run validators, return the instance or throw `InvalidOperationException` on first validator failure.
+
+Extension-style conveniences (project-specific, common patterns):
+
+- `RequireNotEmpty(Func selector, string name)` — validate string properties are not empty.
+- `RequireRange(Func selector, int min, int max, string name)` — inclusive numeric range validator.
+
+## Examples
+
+1) Simple configuration
+
+```csharp
+var p = MutableBuilder
+ .New(static () => new Person())
+ .With(p => p.Name = "Ada")
+ .With(p => p.Age = 30)
+ .Build(); // { Name = "Ada", Age = 30 }
+```
+
+2) Mutations applied in order
+
+```csharp
+var b = MutableBuilder.New(() => new Person())
+ .With(p => p.Steps.Add("A"))
+ .With(p => p.Steps.Add("B"));
+
+var first = b.Build(); // Steps == ["A","B"]
+b.With(p => p.Steps.Add("C"));
+var second = b.Build(); // Steps == ["A","B","C"]
+```
+
+3) Validation failure
+
+```csharp
+var b = MutableBuilder.New(() => new Person())
+ .With(p => p.Name = "")
+ .Require(p => string.IsNullOrEmpty(p.Name) ? "Name must be non-empty." : null);
+
+_ = Record.Exception(() => b.Build()); // InvalidOperationException with message
+```
+
+## Testing tips
+
+- Test mutation order by appending actions that record to a shared list on the instance.
+- Test validation by registering multiple validators and asserting the first failing validator message is thrown.
+- Verify builder reuse by calling `Build()` multiple times after registering additional mutations.
+
+## Why use it
+
+- Explicit, readable configuration in tests and factories.
+- Predictable behavior: deterministic mutation and validation order.
+- Low overhead: only lists during configuration and one factory call + validators per `Build()`.
+
+## Gotchas
+
+- Builders are mutable and not thread-safe. Freeze semantics are not provided — callers must avoid concurrent mutations.
+- Prefer `static` lambdas to avoid closure allocations in hot paths.
+- Validators must return `null` on success; any non-`null` string is treated as the error message returned to the caller via `InvalidOperationException`.
\ No newline at end of file
diff --git a/docs/patterns/index.md b/docs/patterns/index.md
new file mode 100644
index 0000000..74d628b
--- /dev/null
+++ b/docs/patterns/index.md
@@ -0,0 +1,70 @@
+# Patterns
+
+Welcome! This section is the **reference home** for PatternKit’s core building blocks. Each page explains the *why*, the *shape* (APIs), and gives a tiny snippet so you can drop the pattern straight into your codebase.
+
+If you’re looking for end-to-end, production-shaped demos, check the **Examples & Demos** section—those pages show these patterns working together (auth/logging chains, payment pipelines, router, coercer, etc.).
+
+---
+
+## How these fit together
+
+* **Behavioral** patterns describe *what runs & when* (chains and strategies).
+* **Creational** helpers build immutable, fast artifacts (routers, pipelines) from tiny delegates.
+* Common themes:
+
+ * **First-match wins** (predictable branching without `if` ladders).
+ * **Branchless rule packs** (`ActionChain` with `When/ThenContinue/ThenStop/Finally`).
+ * **Immutable after `Build()`** (thread-safe, allocation-light hot paths).
+
+---
+
+## Behavioral
+
+### Chain
+
+* **[Behavioral.Chain.ActionChain](behavioral/chain/actionchain.md)**
+ Compose linear rule packs with explicit continue/stop semantics and an always-runs `Finally`.
+
+* **[Behavioral.Chain.ResultChain](behavioral/chain/resultchain.md)**
+ Like `ActionChain`, but each step returns a result; first failure short-circuits.
+
+### Strategy
+
+* **[Behavioral.Strategy.Strategy](behavioral/strategy/strategy.md)**
+ Simple strategy selection—pick exactly one handler.
+
+* **[Behavioral.Strategy.TryStrategy](behavioral/strategy/trystrategy.md)**
+ First-success wins: chain of `Try(in, out)` handlers; great for parsing/coercion.
+
+* **[Behavioral.Strategy.ActionStrategy](behavioral/strategy/actionstrategy.md)**
+ Fire one or more actions (no result value) based on predicates.
+
+* **[Behavioral.Strategy.AsyncStrategy](behavioral/strategy/asyncstrategy.md)**
+ Async sibling for strategies that await external work.
+
+---
+
+## Creational (Builder)
+
+* **[Creational.Builder.BranchBuilder](creational/builder/branchbuilder.md)**
+ Zero-`if` router: register `(predicate → step)` pairs; emits a tight first-match loop.
+
+* **[Creational.Builder.ChainBuilder](creational/builder/chainbuilder.md)**
+ Small helper to accumulate steps, then project into your own pipeline type.
+
+* **[Creational.Builder.Composer](creational/builder/composer.md)**
+ Compose multiple builders/artifacts into a single product.
+
+* **[Creational.Builder.MutableBuilder](creational/builder/mutablebuilder.md)**
+ A lightweight base for fluent, mutable configuration objects.
+
+---
+
+## Where to see them in action
+
+* **Auth & Logging Chain** — request-ID logging + strict auth short-circuit using `ActionChain`.
+* **Strategy-Based Coercion** — `TryStrategy` turns mixed inputs into typed values.
+* **Mediated / Config-Driven Transaction Pipelines** — chains + strategies for totals, rounding, tender routing.
+* **Minimal Web Request Router** — `BranchBuilder` for middleware and routes.
+
+> Tip: every pattern page has a tiny example; the demos show realistic combinations with TinyBDD tests you can read like specs.
diff --git a/docs/patterns/toc.yml b/docs/patterns/toc.yml
new file mode 100644
index 0000000..2271782
--- /dev/null
+++ b/docs/patterns/toc.yml
@@ -0,0 +1,33 @@
+- name: Patterns
+ href: index.md
+ items:
+ - name: Behavioral
+ items:
+ - name: Chain
+ items:
+ - name: Behavioral.Chain.ActionChain
+ href: behavioral/chain/actionchain.md
+ - name: Behavioral.Chain.ResultChain
+ href: behavioral/chain/resultchain.md
+ - name: Strategy
+ items:
+ - name: Behavioral.Strategy.Strategy
+ href: behavioral/strategy/strategy.md
+ - name: Behavioral.Strategy.TryStrategy
+ href: behavioral/strategy/trystrategy.md
+ - name: Behavioral.Strategy.ActionStrategy
+ href: behavioral/strategy/actionstrategy.md
+ - name: Behavioral.Strategy.AsyncStrategy
+ href: behavioral/strategy/asyncstrategy.md
+ - name: Creational
+ items:
+ - name: Builder
+ items:
+ - name: Creational.Builder.BranchBuilder
+ href: creational/builder/branchbuilder.md
+ - name: Creational.Builder.ChainBuilder
+ href: creational/builder/chainbuilder.md
+ - name: Creational.Builder.Composer
+ href: creational/builder/composer.md
+ - name: Creational.Builder.MutableBuilder
+ href: creational/builder/mutablebuilder.md
diff --git a/docs/toc.yml b/docs/toc.yml
index db45c00..6d33da5 100644
--- a/docs/toc.yml
+++ b/docs/toc.yml
@@ -3,4 +3,12 @@
- name: API Reference
href: api/
- homepage: api/toc.md
+ homepage: api/
+
+- name: Patterns
+ href: patterns/
+ homepage: patterns/
+
+- name: Examples
+ href: examples/
+ homepage: examples/
diff --git a/src/PatternKit.Core/Behavioral/Chain/ActionChain.cs b/src/PatternKit.Core/Behavioral/Chain/ActionChain.cs
new file mode 100644
index 0000000..4a336e5
--- /dev/null
+++ b/src/PatternKit.Core/Behavioral/Chain/ActionChain.cs
@@ -0,0 +1,266 @@
+using System.Runtime.CompilerServices;
+
+namespace PatternKit.Behavioral.Chain;
+
+///
+/// A tiny, middleware-style pipeline for where each handler decides
+/// to call next or short-circuit the chain.
+///
+/// The context type threaded through the chain.
+///
+///
+/// is built from a set of ordered handlers. Each handler receives the
+/// current context and a next delegate. A handler can either:
+///
+///
+/// - Invoke next(in ctx) to continue the chain, or
+/// - Return without calling next to short-circuit the chain.
+///
+///
+/// Registration order is preserved. A terminal handler can be provided;
+/// it runs only if the chain was not short-circuited earlier (i.e., a previous step called next).
+/// After the chain is immutable and thread-safe.
+///
+///
+///
+///
+/// var log = new List<string>();
+///
+/// var chain = ActionChain<HttpRequest>.Create()
+/// // Log request id but continue
+/// .When((in r) => r.Headers.ContainsKey("X-Request-Id"))
+/// .ThenContinue(r => log.Add($"reqid={r.Headers["X-Request-Id"]}"))
+///
+/// // Deny missing auth for /admin/* and STOP the chain
+/// .When((in r) => r.Path.StartsWith("/admin", StringComparison.Ordinal) &&
+/// !r.Headers.ContainsKey("Authorization"))
+/// .ThenStop(r => log.Add("deny: missing auth"))
+///
+/// // Tail: logs method/path only if earlier steps continued
+/// .Finally((in r, next) => { log.Add($"{r.Method} {r.Path}"); next(in r); })
+/// .Build();
+///
+/// chain.Execute(new HttpRequest("GET", "/health", new Dictionary<string, string>()));
+/// chain.Execute(new HttpRequest("GET", "/admin/metrics", new Dictionary<string, string>()));
+/// // log: ["GET /health", "deny: missing auth", "GET /admin/metrics"]
+///
+///
+public sealed class ActionChain
+{
+ ///
+ /// Delegate representing the continuation of the chain.
+ ///
+ /// The current context.
+ public delegate void Next(in TCtx ctx);
+
+ ///
+ /// Delegate representing a chain handler.
+ ///
+ /// The current context.
+ ///
+ /// The continuation to invoke to proceed to the next handler. If a handler returns without
+ /// calling , the chain short-circuits.
+ ///
+ public delegate void Handler(in TCtx ctx, Next next);
+
+ ///
+ /// Delegate representing a predicate over the context.
+ ///
+ /// The current context.
+ /// if the condition is met; otherwise .
+ public delegate bool Predicate(in TCtx ctx);
+
+ private readonly Next _entry;
+
+ private ActionChain(Next entry) => _entry = entry;
+
+ ///
+ /// Executes the composed chain for the provided context.
+ ///
+ /// The context value passed through the chain.
+ ///
+ /// Execution starts at the first registered handler. Handlers may short-circuit by not calling next.
+ /// The terminal continuation is a no-op; calling it in the tail is optional.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Execute(in TCtx ctx) => _entry(in ctx);
+
+ ///
+ /// Fluent builder for .
+ ///
+ ///
+ ///
+ /// Use to append middleware, to add conditional
+ /// blocks, and to set a terminal tail that runs only if the chain was
+ /// not short-circuited. Call to compose an immutable chain.
+ ///
+ /// Thread safety: The built chain is immutable and thread-safe; the builder is not.
+ ///
+ public sealed class Builder
+ {
+ private readonly List _handlers = new(8);
+ private Handler? _tail;
+
+ ///
+ /// Adds a middleware handler to the end of the chain.
+ ///
+ /// The handler to append.
+ /// The same builder for chaining.
+ ///
+ /// The controls flow by deciding whether to call next.
+ ///
+ public Builder Use(Handler handler)
+ {
+ _handlers.Add(handler);
+ return this;
+ }
+
+ ///
+ /// Starts a conditional block. If the predicate is false, the chain automatically continues.
+ ///
+ /// The condition to evaluate for the current context.
+ /// A that configures the conditional behavior.
+ ///
+ /// The generated handler ensures that when returns
+ /// , next is called automatically.
+ ///
+ public WhenBuilder When(Predicate predicate) => new(this, predicate);
+
+ ///
+ /// Sets the terminal tail handler. It runs only if earlier handlers called next all the way through.
+ ///
+ /// The tail handler to run at the end of the chain.
+ /// The same builder for chaining.
+ ///
+ /// If an earlier handler returns without calling next, the tail does not run.
+ ///
+ public Builder Finally(Handler tail)
+ {
+ _tail = tail;
+ return this;
+ }
+
+ ///
+ /// Composes all registered handlers (and optional tail) into a single immutable chain.
+ ///
+ /// An executable instance.
+ ///
+ /// Handlers are composed in reverse registration order so that execution preserves the original order.
+ /// A terminal no-op continuation is used; the tail may call next safely.
+ ///
+ public ActionChain Build()
+ {
+ // terminal "no-op" continuation
+ Next next = static (in _) => { };
+
+ if (_tail is not null)
+ {
+ var t = _tail;
+ var prev = next;
+ next = (in c) => t(in c, prev);
+ }
+
+ for (var i = _handlers.Count - 1; i >= 0; i--)
+ {
+ var h = _handlers[i];
+ var prev = next;
+ next = (in c) => h(in c, prev);
+ }
+
+ return new ActionChain(next);
+ }
+
+ ///
+ /// Builder for a conditional branch created via .
+ ///
+ public sealed class WhenBuilder
+ {
+ private readonly Builder _owner;
+ private readonly Predicate _pred;
+
+ ///
+ /// Initializes a new .
+ ///
+ /// The parent builder.
+ /// The predicate to guard the conditional handler(s).
+ internal WhenBuilder(Builder owner, Predicate pred) => (_owner, _pred) = (owner, pred);
+
+ ///
+ /// Adds a conditional handler: when the predicate is , run ;
+ /// otherwise automatically continue the chain.
+ ///
+ /// The handler to execute when the predicate is true.
+ /// The parent builder for chaining.
+ ///
+ /// The provided may itself call or omit next to continue or stop.
+ ///
+ public Builder Do(Handler handler)
+ {
+ var pred = _pred; // avoid capturing 'this'
+ _owner.Use((in c, next) =>
+ {
+ if (pred(in c)) handler(in c, next);
+ else next(in c);
+ });
+ return _owner;
+ }
+
+ ///
+ /// Adds a conditional action that executes and stops the chain when the predicate is true.
+ ///
+ /// The action to perform before short-circuiting.
+ /// The parent builder for chaining.
+ ///
+ /// When the predicate is false, the chain continues automatically.
+ ///
+ public Builder ThenStop(Action action)
+ {
+ var pred = _pred;
+ _owner.Use((in c, next) =>
+ {
+ if (pred(in c))
+ {
+ action(c);
+ return;
+ }
+
+ next(in c);
+ });
+ return _owner;
+ }
+
+ ///
+ /// Adds a conditional action that executes and then continues the chain when the predicate is true.
+ ///
+ /// The action to perform before continuing.
+ /// The parent builder for chaining.
+ ///
+ /// When the predicate is false, the chain continues automatically.
+ ///
+ public Builder ThenContinue(Action action)
+ {
+ var pred = _pred;
+ _owner.Use((in c, next) =>
+ {
+ if (pred(in c)) action(c);
+ next(in c);
+ });
+ return _owner;
+ }
+ }
+ }
+
+ ///
+ /// Starts a new to configure an .
+ ///
+ /// A fresh builder instance.
+ ///
+ ///
+ /// var chain = ActionChain<MyCtx>.Create()
+ /// .Use((in c, next) => { /* pre */ next(in c); })
+ /// .Finally((in c, next) => { /* tail */ next(in c); })
+ /// .Build();
+ ///
+ ///
+ public static Builder Create() => new();
+}
\ No newline at end of file
diff --git a/src/PatternKit.Core/Behavioral/Chain/ResultChain.cs b/src/PatternKit.Core/Behavioral/Chain/ResultChain.cs
new file mode 100644
index 0000000..d38b4f3
--- /dev/null
+++ b/src/PatternKit.Core/Behavioral/Chain/ResultChain.cs
@@ -0,0 +1,249 @@
+using System.Runtime.CompilerServices;
+
+namespace PatternKit.Behavioral.Chain;
+
+///
+/// A first-match-wins chain that can produce a value.
+/// Each handler receives (in TIn input, out TOut? result, Next next) and may either:
+///
+/// - Produce a value (set result and return ) to short-circuit, or
+/// - Delegate to next(input, out result) so later handlers can attempt to produce.
+///
+///
+/// The input type threaded through the chain.
+/// The potential output type produced by handlers.
+///
+///
+/// Use when you want ordered rules that compute and return a value
+/// (e.g., routing to an HttpResponse, choosing a price/promotion, parsing commands).
+/// If you only need side effects, prefer .
+///
+/// Execution semantics
+///
+/// - Handlers are evaluated in registration order.
+/// - The first handler that returns wins and the chain stops.
+/// - If no handler produces and no is configured, returns and result is .
+/// - If a Finally tail is set, it runs only if the chain reaches the tail (i.e., nobody produced earlier). It typically acts as a default/NotFound.
+///
+///
+/// Performance: The chain composes to a single delegate at time; the built chain is immutable and thread-safe.
+///
+///
+///
+/// A tiny router that returns a value:
+/// .Create()
+/// .When(static (in r) => r.Method == "GET" && r.Path == "/health")
+/// .Then(r => new Response(200, "OK"))
+/// .When(static (in r) => r.Method == "GET" && r.Path.StartsWith("/users/"))
+/// .Then(r => new Response(200, $"user:{r.Path[7..]}"))
+/// // default / not found
+/// .Finally(static (in _, out Response? res, _) => { res = new(404, "not found"); return true; })
+/// .Build();
+///
+/// var ok1 = router.Execute(in new Request("GET", "/health"), out var res1); // true, 200 OK
+/// var ok2 = router.Execute(in new Request("GET", "/nope"), out var res2); // true, 404
+/// ]]>
+///
+public sealed class ResultChain
+{
+ ///
+ /// Delegate representing the continuation of the chain.
+ /// Implementations should return when a downstream handler produced a result.
+ ///
+ /// The current input value.
+ /// The produced result when any downstream handler succeeds.
+ /// if a result was produced; otherwise .
+ public delegate bool Next(in TIn input, out TOut? result);
+
+ ///
+ /// Delegate representing a chain handler that may produce a value or delegate to .
+ ///
+ /// The current input value.
+ /// The result produced by this handler or by a downstream handler.
+ /// The continuation to invoke if this handler chooses not to (or cannot) produce.
+ ///
+ /// if this handler (or a downstream handler) produced a result; otherwise .
+ /// Returning short-circuits the chain.
+ ///
+ public delegate bool TryHandler(in TIn input, out TOut? result, Next next);
+
+ ///
+ /// Delegate representing a predicate over the input.
+ ///
+ /// The current input value.
+ /// if the condition holds; otherwise .
+ public delegate bool Predicate(in TIn input);
+
+ private readonly Next _entry;
+
+ private ResultChain(Next entry) => _entry = entry;
+
+ ///
+ /// Executes the composed chain.
+ ///
+ /// The input value to evaluate.
+ /// When the method returns , contains the produced result; otherwise .
+ /// if any handler (or the tail) produced a result; otherwise .
+ ///
+ /// The terminal continuation returns and leaves as .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Execute(in TIn input, out TOut? result) => _entry(in input, out result);
+
+ ///
+ /// Fluent builder for .
+ ///
+ ///
+ /// Use to append raw handlers, to start a conditional section,
+ /// and to set a terminal fallback that runs only when no prior handler produced a result.
+ /// Call to compose an immutable, thread-safe chain.
+ ///
+ public sealed class Builder
+ {
+ private readonly List _handlers = new(8);
+ private TryHandler? _tail;
+
+ ///
+ /// Appends a handler to the chain.
+ ///
+ /// The handler to add. It may produce a result or delegate to the provided next.
+ /// The same builder for chaining.
+ public Builder Use(TryHandler handler)
+ {
+ _handlers.Add(handler);
+ return this;
+ }
+
+ ///
+ /// Starts a conditional block guarded by .
+ ///
+ /// The condition to evaluate for the current input.
+ /// A that configures behavior when the predicate is .
+ ///
+ /// When the predicate is , the generated handler automatically delegates to the next step.
+ ///
+ public WhenBuilder When(Predicate predicate) => new(this, predicate);
+
+ ///
+ /// Sets a terminal fallback (e.g., default/NotFound) that runs only if the chain reaches the tail.
+ ///
+ /// The tail handler to execute at the end of the chain.
+ /// The same builder for chaining.
+ ///
+ /// If any earlier handler produced a result (returned ), the tail is not invoked.
+ ///
+ public Builder Finally(TryHandler tail)
+ {
+ _tail = tail;
+ return this;
+ }
+
+ ///
+ /// Composes all registered handlers (and optional tail) into a single immutable chain.
+ ///
+ /// An executable instance.
+ ///
+ /// Handlers are folded from last to first so that runtime execution preserves registration order.
+ /// The terminal continuation returns and result.
+ ///
+ public ResultChain Build()
+ {
+ // terminal: no result
+ Next next = static (in _, out r) =>
+ {
+ r = default;
+ return false;
+ };
+
+ if (_tail is not null)
+ {
+ var t = _tail;
+ var prev = next;
+ next = (in x, out r) => t(in x, out r, prev);
+ }
+
+ for (var i = _handlers.Count - 1; i >= 0; i--)
+ {
+ var h = _handlers[i];
+ var prev = next;
+ next = (in x, out r) => h(in x, out r, prev);
+ }
+
+ return new ResultChain(next);
+ }
+
+ ///
+ /// Builder for a conditional branch created via .
+ ///
+ public sealed class WhenBuilder
+ {
+ private readonly Builder _owner;
+ private readonly Predicate _pred;
+
+ ///
+ /// Initializes a new .
+ ///
+ /// The parent builder.
+ /// The predicate guarding the conditional handler(s).
+ internal WhenBuilder(Builder owner, Predicate pred) => (_owner, _pred) = (owner, pred);
+
+ ///
+ /// Adds a conditional handler that may produce a value or delegate to the next step.
+ /// When the predicate is , the chain automatically delegates.
+ ///
+ /// The conditional handler.
+ /// The parent builder for chaining.
+ public Builder Do(TryHandler handler)
+ {
+ var pred = _pred;
+ _owner.Use((in x, out r, next)
+ => pred(in x)
+ ? handler(in x, out r, next)
+ : next(in x, out r));
+ return _owner;
+ }
+
+ ///
+ /// Adds a conditional producer that returns a value and stops the chain when the predicate is .
+ ///
+ /// A function that maps the input to the produced result.
+ /// The parent builder for chaining.
+ ///
+ /// When the predicate is , the chain automatically delegates to the next step.
+ ///
+ public Builder Then(Func produce)
+ {
+ var pred = _pred;
+ _owner.Use((in x, out r, next) =>
+ {
+ if (!pred(in x))
+ return next(in x, out r);
+
+ r = produce(x);
+ return true;
+ });
+ return _owner;
+ }
+ }
+ }
+
+ ///
+ /// Starts a new for configuring a .
+ ///
+ /// A fresh builder instance.
+ ///
+ /// .Create()
+ /// .When(static (in i) => i > 0).Then(i => $"+{i}")
+ /// .Finally(static (in _, out string? r, _) => { r = "default"; return true; })
+ /// .Build();
+ /// ]]>
+ ///
+ public static Builder Create() => new();
+}
\ No newline at end of file
diff --git a/src/PatternKit.Core/Behavioral/Strategy/ActionStrategy.cs b/src/PatternKit.Core/Behavioral/Strategy/ActionStrategy.cs
new file mode 100644
index 0000000..56cea05
--- /dev/null
+++ b/src/PatternKit.Core/Behavioral/Strategy/ActionStrategy.cs
@@ -0,0 +1,186 @@
+using PatternKit.Common;
+using PatternKit.Creational.Builder;
+
+namespace PatternKit.Behavioral.Strategy;
+
+///
+/// Represents a "first-match-wins" strategy pipeline built from predicate/action pairs,
+/// where actions perform side effects and do not return a value.
+///
+/// The input type accepted by each predicate and action.
+///
+///
+/// is the action-only counterpart to
+/// and .
+/// It uses delegates to decide whether an
+/// applies. The first predicate that returns determines which action is executed.
+///
+///
+/// If no predicates match:
+///
+///
+/// - calls the configured default action if present; otherwise throws via .
+/// - returns if any action ran (including default); otherwise .
+///
+/// Thread-safety: The built strategy is immutable and thread-safe. The is not thread-safe.
+///
+///
+///
+/// var log = new List<string>();
+///
+/// void Log(string msg) => log.Add(msg);
+///
+/// var s = ActionStrategy<int>.Create()
+/// .When(static i => i > 0).Then(static i => Log($"+{i}"))
+/// .When(static i => i < 0).Then(static i => Log($"-{i}"))
+/// .Default(static _ => Log("zero"))
+/// .Build();
+///
+/// s.Execute(5); // logs "+5"
+/// s.Execute(-3); // logs "-3"
+/// s.Execute(0); // logs "zero"
+///
+///
+public sealed class ActionStrategy
+{
+ ///
+ /// Delegate representing a predicate used to test the input value.
+ ///
+ /// The input value.
+ /// if this predicate matches; otherwise .
+ public delegate bool Predicate(in TIn input);
+
+ ///
+ /// Delegate representing an action that runs when its corresponding predicate matches.
+ ///
+ /// The input value.
+ public delegate void ActionHandler(in TIn input);
+
+ private readonly Predicate[] _predicates;
+ private readonly ActionHandler[] _actions;
+ private readonly bool _hasDefault;
+ private readonly ActionHandler _default;
+
+ private static ActionHandler Noop => static (in _) => { };
+
+
+ private ActionStrategy(Predicate[] predicates, ActionHandler[] actions, bool hasDefault, ActionHandler @default)
+ => (_predicates, _actions, _hasDefault, _default) = (predicates, actions, hasDefault, @default);
+
+ ///
+ /// Executes the first matching action for the given .
+ ///
+ /// The input value.
+ ///
+ /// Iterates predicates in registration order; runs the corresponding action for the first match and returns.
+ /// If no predicate matches and a default was configured via ,
+ /// the default action runs. Otherwise, throws via .
+ ///
+ ///
+ /// Thrown when no predicates match and no default action is configured.
+ ///
+ public void Execute(in TIn input)
+ {
+ var predicates = _predicates;
+ for (var i = 0; i < predicates.Length; i++)
+ if (predicates[i](in input))
+ {
+ _actions[i](in input);
+ return;
+ }
+
+ if (_hasDefault)
+ {
+ _default(in input);
+ return;
+ }
+
+ Throw.NoStrategyMatched();
+ }
+
+ ///
+ /// Attempts to execute the first matching action for the given .
+ ///
+ /// The input value.
+ ///
+ /// if an action (or default) executed; otherwise .
+ ///
+ ///
+ /// Unlike , this method never throws due to no matches.
+ ///
+ public bool TryExecute(in TIn input)
+ {
+ var predicates = _predicates;
+ for (var i = 0; i < predicates.Length; i++)
+ if (predicates[i](in input))
+ {
+ _actions[i](in input);
+ return true;
+ }
+
+ if (!_hasDefault)
+ return false;
+
+ _default(in input);
+ return true;
+
+ }
+
+ ///
+ /// Provides a fluent API for constructing an .
+ ///
+ ///
+ /// Use to start a branch and to attach an action.
+ /// Optionally add a that runs when no predicates match.
+ /// Call to produce an immutable, thread-safe strategy.
+ ///
+ public sealed class Builder
+ {
+ private readonly BranchBuilder _core = BranchBuilder.Create();
+
+ public WhenBuilder When(Predicate predicate) => new(this, predicate);
+
+ public Builder Default(ActionHandler action)
+ {
+ _core.Default(action);
+ return this;
+ }
+
+ public ActionStrategy Build()
+ => _core.Build(
+ fallbackDefault: Noop,
+ projector: static (predicates, handlers, hasDefault, @default)
+ => new ActionStrategy(predicates, handlers, hasDefault, @default));
+
+ public sealed class WhenBuilder
+ {
+ private readonly Builder _owner;
+ private readonly Predicate _pred;
+ internal WhenBuilder(Builder owner, Predicate pred) => (_owner, _pred) = (owner, pred);
+
+ public Builder Then(ActionHandler action)
+ {
+ _owner._core.Add(_pred, action);
+ return _owner;
+ }
+ }
+ }
+
+
+ ///
+ /// Creates a new for constructing an .
+ ///
+ /// A new instance.
+ ///
+ ///
+ /// var s = ActionStrategy<string>.Create()
+ /// .When(static s => string.IsNullOrEmpty(s)).Then(static _ => Console.WriteLine("empty"))
+ /// .Default(static _ => Console.WriteLine("other"))
+ /// .Build();
+ ///
+ /// s.Execute(""); // prints "empty"
+ /// s.TryExecute("x"); // prints "other", returns true
+ ///
+ ///
+ public static Builder Create() => new();
+}
\ No newline at end of file
diff --git a/src/PatternKit.Core/Behavioral/Strategy/AsyncStrategy.cs b/src/PatternKit.Core/Behavioral/Strategy/AsyncStrategy.cs
new file mode 100644
index 0000000..54fb317
--- /dev/null
+++ b/src/PatternKit.Core/Behavioral/Strategy/AsyncStrategy.cs
@@ -0,0 +1,217 @@
+using PatternKit.Common;
+using PatternKit.Creational.Builder;
+
+namespace PatternKit.Behavioral.Strategy;
+
+///
+/// Composable, asynchronous strategy that selects the first matching branch
+/// (predicate + handler) and executes its handler.
+///
+/// The input type supplied to predicates and handlers.
+/// The result type returned by handlers.
+///
+///
+/// This strategy evaluates predicates in the order they were added. The first
+/// predicate that returns determines the chosen handler.
+/// If no predicates match, an optional handler
+/// is invoked. Without a default,
+/// throws to signal that no branch matched.
+///
+///
+/// Instances built via are immutable and thread-safe
+/// for concurrent execution, assuming supplied predicates/handlers are thread-safe.
+///
+///
+///
+/// Basic usage:
+///
+/// var strat = AsyncStrategy<int, string>.Create()
+/// .When((n, ct) => new ValueTask<bool>(n < 0))
+/// .Then((n, ct) => new ValueTask<string>("negative"))
+/// .When((n, ct) => new ValueTask<bool>(n == 0))
+/// .Then((n, ct) => new ValueTask<string>("zero"))
+/// .Default((n, ct) => new ValueTask<string>("positive"))
+/// .Build();
+///
+/// var result = await strat.ExecuteAsync(5, CancellationToken.None); // "positive"
+///
+///
+///
+public sealed class AsyncStrategy
+{
+ ///
+ /// Asynchronous predicate delegate that decides whether a branch can handle the input.
+ ///
+ /// The input value to evaluate.
+ /// A cancellation token.
+ ///
+ /// A producing if the branch
+ /// should handle the input; otherwise .
+ ///
+ public delegate ValueTask Predicate(TIn input, CancellationToken ct);
+
+ ///
+ /// Asynchronous handler delegate that produces the result for a matching branch.
+ ///
+ /// The input value to handle.
+ /// A cancellation token.
+ ///
+ /// A producing the result for the branch.
+ ///
+ public delegate ValueTask Handler(TIn input, CancellationToken ct);
+
+ private readonly Predicate[] _predicates;
+ private readonly Handler[] _handlers;
+ private readonly bool _hasDefault;
+ private readonly Handler _default;
+
+ // Internal fallback used by the builder when a default is not provided.
+ private static Handler DefaultResult =>
+ (_, _) => new ValueTask(default(TOut)!);
+
+ private AsyncStrategy(Predicate[] preds, Handler[] handlers, bool hasDefault, Handler def) =>
+ (_predicates, _handlers, _hasDefault, _default) = (preds, handlers, hasDefault, def);
+
+ ///
+ /// Executes the strategy by evaluating predicates in order and invoking the first matching handler.
+ ///
+ /// The input value passed to predicates and handlers.
+ /// An optional cancellation token.
+ ///
+ /// A that completes with the result of the chosen handler.
+ ///
+ ///
+ /// If no predicates match and a default handler was configured, the default handler is invoked.
+ /// Otherwise the method throws to indicate that no branch matched.
+ ///
+ ///
+ /// Thrown when no predicate matches and no default handler is configured.
+ ///
+ public async ValueTask ExecuteAsync(TIn input, CancellationToken ct = default)
+ {
+ var preds = _predicates;
+ for (var i = 0; i < preds.Length; i++)
+ if (await preds[i](input, ct).ConfigureAwait(false))
+ return await _handlers[i](input, ct).ConfigureAwait(false);
+
+ if (_hasDefault)
+ return await _default(input, ct).ConfigureAwait(false);
+
+ return Throw.NoStrategyMatched();
+ }
+
+ ///
+ /// Fluent builder for composing branches.
+ ///
+ ///
+ /// Use to add predicate/handler branches and
+ /// to set an optional fallback handler. Supports
+ /// synchronous adapters for convenience—see ,
+ /// ,
+ /// and .
+ ///
+ public sealed class Builder
+ {
+ private readonly BranchBuilder _core = BranchBuilder.Create();
+
+ ///
+ /// Adds a new branch that will be considered during execution.
+ ///
+ /// An asynchronous predicate for the branch.
+ ///
+ /// A that allows specifying the corresponding handler
+ /// via .
+ ///
+ /// Thrown when is .
+ public WhenBuilder When(Predicate pred) => new(this, pred);
+
+ ///
+ /// Sets the default (fallback) handler used when no predicates match.
+ ///
+ /// The asynchronous default handler.
+ /// The current for chaining.
+ /// Thrown when is .
+ public Builder Default(Handler handler)
+ {
+ _core.Default(handler);
+ return this;
+ }
+
+ ///
+ /// Finalizes the configuration and creates an immutable .
+ ///
+ /// An immutable strategy instance.
+ public AsyncStrategy Build() =>
+ _core.Build(
+ fallbackDefault: DefaultResult,
+ projector: static (p, h, hasDef, def) => new AsyncStrategy(p, h, hasDef, def));
+
+ ///
+ /// Intermediate builder returned by allowing a corresponding handler to be set.
+ ///
+ public sealed class WhenBuilder
+ {
+ private readonly Builder _owner;
+ private readonly Predicate _pred;
+
+ internal WhenBuilder(Builder owner, Predicate pred)
+ {
+ _owner = owner;
+ _pred = pred;
+ }
+
+ ///
+ /// Assigns the handler to execute when the associated predicate evaluates to .
+ ///
+ /// The asynchronous handler for this branch.
+ /// The parent for further configuration.
+ /// Thrown when is .
+ public Builder Then(Handler handler)
+ {
+ _owner._core.Add(_pred, handler);
+ return _owner;
+ }
+ }
+
+ // -------- Synchronous adapters --------
+
+ ///
+ /// Adds a branch using a synchronous predicate (no ).
+ ///
+ /// Synchronous predicate; wrapped as an async predicate.
+ /// A to specify the handler.
+ public WhenBuilder When(Func syncPred)
+ {
+ return When(Adapter);
+ ValueTask Adapter(TIn x, CancellationToken _) => new ValueTask