diff --git a/Directory.Packages.props b/Directory.Packages.props
index cd5fe5a..d16ef73 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -89,7 +89,7 @@
-
+
diff --git a/docs/patternkit-adoption.md b/docs/patternkit-adoption.md
index dfa3bca..0f17535 100644
--- a/docs/patternkit-adoption.md
+++ b/docs/patternkit-adoption.md
@@ -1,7 +1,7 @@
# PatternKit Adoption Inventory
-**PatternKit version:** 0.105.0
-**Last updated:** 2026-05-22 (Phase I coverage tightening)
+**PatternKit version:** 0.112.0
+**Last updated:** 2026-05-22 (feat/consume-patternkit-0.112 — WireTapStep adopted)
This document lists every point in the WorkflowFramework codebase where a PatternKit primitive is used, and every point where a step is intentionally kept bespoke with the rationale for that decision. This is the canonical reference for Phase I and future phases.
@@ -44,6 +44,19 @@ This document lists every point in the WorkflowFramework codebase where a Patter
| **Test coverage** | `tests/WorkflowFramework.Tests.TinyBDD/Integration/Routing/ContentBasedRouterStepScenarios.cs` |
| **Public API change** | None — swap is internal-only. |
+### 4. `WireTapStep` — Wire tap channel pattern
+
+| Item | Detail |
+|------|--------|
+| **File** | `src/WorkflowFramework.Extensions.Integration/Channel/WireTapStep.cs` |
+| **PatternKit namespace** | `PatternKit.Messaging.Channels` |
+| **Primitive** | `AsyncWireTap` |
+| **Purpose** | Wraps the caller-supplied `Func` into a PatternKit tap handler. The `swallowErrors` constructor parameter maps to `TapErrorPolicy.Swallow` / `TapErrorPolicy.Propagate`. The `IWorkflowContext` is wrapped in a `Message` for transit and unwrapped inside the tap lambda — no public API or behavioral change. |
+| **Phase introduced** | feat/consume-patternkit-0.112 |
+| **Test coverage** | `tests/WorkflowFramework.Tests.TinyBDD/Integration/Channel/WireTapStepScenarios.cs` — all 8 Phase G.3 scenarios pass without modification. |
+| **Public API change** | None — swap is internal-only. |
+| **Net delta** | −15 lines of bespoke logic (try/catch error-swallowing replaced by `TapErrorPolicy`). |
+
---
## Intentionally Bespoke
@@ -146,13 +159,9 @@ The following EIP steps and other components were evaluated against PatternKit 0
| **Rationale** | PatternKit `AsyncAdapter` is a type-mapping pattern (produce `TOut` from `TIn`). `ChannelAdapterStep` is a side-effect operation (send/receive via `IChannelAdapter`). The send/receive contract doesn't fit the adapt-a-type signature. |
| **Test coverage** | Phase G.3 characterization tests |
-#### `WireTapStep`
+#### ~~`WireTapStep`~~ — **Adopted in feat/consume-patternkit-0.112**
-| Item | Detail |
-|------|--------|
-| **File** | `src/WorkflowFramework.Extensions.Integration/Channel/WireTapStep.cs` |
-| **Rationale** | Core contract (run a side-effect without disrupting the main flow, with optional error swallowing) is simpler than PatternKit's `AsyncActionDecorator` pipeline. `AsyncActionDecorator` wraps a component and transforms/intercepts results; `WireTapStep` purely fires-and-forgets a side channel. |
-| **Test coverage** | Phase G.3 characterization tests |
+Moved to the **Adopted** section above. Now delegates to `PatternKit.Messaging.Channels.AsyncWireTap`.
#### `MessageBridgeStep`
@@ -186,12 +195,18 @@ When evaluating a bespoke component for PatternKit adoption, the following crite
## Future Evaluation Targets
-The following components are candidates for PatternKit adoption in later phases if suitable primitives become available:
+The following components are candidates for PatternKit adoption in later phases if suitable primitives become available or interface alignment is achieved:
-| Component | Potential Primitive | Blocking Reason Today |
+| Component | Potential Primitive | Blocking Reason (assessed against 0.112.0) |
|-----------|--------------------|-----------------------|
-| `AggregatorStep` | PatternKit Aggregator (future) | No primitive in 0.105.0 |
-| `ScatterGatherStep` | PatternKit ScatterGather (future) | No primitive in 0.105.0 |
+| `NormalizerStep` | `Normalizer` (now in 0.112.0) | Behavioral mismatch: PatternKit uses content predicates (first match wins); bespoke uses O(1) dictionary keyed dispatch. Error message format differs — test pins format name in exception text. See `docs/patternkit-followup.md`. |
+| `ContentEnricherStep` | `AsyncContentEnricher` (now in 0.112.0) | Semantic mismatch: PatternKit returns an enriched payload copy; bespoke mutates `IWorkflowContext` in place via a `Func`. Wrapping adds indirection for zero functional benefit. |
+| `IdempotentReceiverStep` | `IdempotentReceiver` (now in 0.112.0) | Behavioral breaking change: PatternKit marks failed attempts as `Failed` in the store (allowing retry); bespoke registers the ID in a `HashSet` before calling inner, so a failed first attempt DOES suppress the second. Test `ReAttemptAfterExceptionIsSkipped` pins this behavior. |
+| `ClaimCheckStep` / `ClaimRetrieveStep` | `ClaimCheck` (now in 0.112.0) | Interface mismatch: bespoke `IClaimCheckStore` is untyped (`object`); PatternKit `IClaimCheckStore` is typed. Bridging requires an adapter class, adding indirection without deleting complexity. |
+| `PollingConsumerStep` | `AsyncPollingConsumer` (now in 0.112.0) | Semantic mismatch: PatternKit is a continuous polling loop (run until cancelled); bespoke is a single-shot poll (`PollAsync` → store results → return). Incompatible lifecycle models. |
+| `ScatterGatherStep` | `AsyncScatterGather` (now in 0.112.0) | Integration complexity: handlers mutate a shared `IWorkflowContext` and write results to named context keys; PatternKit's per-recipient isolation model returns typed `TResponse` values. Adapting while preserving the `__Result_{handler.Name}` and `ResultsKey` contract would re-implement the existing complexity via a wrapper, defeating the purpose. |
+| `TransactionalOutboxStep` | `IOutboxStore` (now in 0.112.0) | Interface mismatch: bespoke `IOutboxStore` uses `SaveAsync(object) → string`; PatternKit `IOutboxStore` uses `EnqueueAsync(Message) → OutboxMessage`. Different return types and message wrapper model. |
+| `AggregatorStep` | PatternKit Aggregator (future) | No Aggregator primitive in 0.112.0 |
| `PluginManager` | `Strategy` + `AbstractFactory` | Phase H.8 — not yet started |
| `AgentLoopStep` / `AgentDecisionStep` | TypeDispatcher | Phase H.7 — not yet started |
| `ResilienceMiddleware` (Polly) | `RetryPolicy` | Phase F pilot option B — deferred |
diff --git a/docs/patternkit-followup.md b/docs/patternkit-followup.md
new file mode 100644
index 0000000..8732674
--- /dev/null
+++ b/docs/patternkit-followup.md
@@ -0,0 +1,243 @@
+# PatternKit Follow-Up — 0.112.0 Adoption Deferrals
+
+**Branch:** `feat/consume-patternkit-0.112`
+**Date assessed:** 2026-05-22
+**Assessor:** Claude Sonnet 4.6 (refactor agent)
+
+This document captures every step evaluated against PatternKit 0.112.0 during
+`feat/consume-patternkit-0.112` that was deferred with the specific reason why. It
+supersedes the equivalent "Future Evaluation Targets" notes in `docs/patternkit-adoption.md`.
+Use this as the starting point for the next PatternKit adoption pass.
+
+---
+
+## Deferred: NormalizerStep → Normalizer
+
+**File:** `src/WorkflowFramework.Extensions.Integration/Transformation/NormalizerStep.cs`
+
+**PatternKit primitive:** `PatternKit.Messaging.Transformation.Normalizer`
+
+**Why deferred:**
+
+PatternKit's `Normalizer` dispatches via *content predicates* evaluated in
+registration order (first-match-wins). `NormalizerStep` dispatches via an O(1) `Dictionary`
+keyed lookup — the format detector returns a string key, not a `bool` predicate.
+
+Two specific blockers:
+
+1. **Predicate vs key dispatch model.** Converting each dictionary entry to a predicate
+ (`raw => format == key`) would change the dispatch from O(1) to O(n) and lose the dictionary
+ key contract from the constructor signature.
+
+2. **Error message behavioral mismatch.** When no format matches, the bespoke step throws
+ `InvalidOperationException($"No translator found for format '{format}' and no default translator configured.")`.
+ The test `UnknownFormatNoDefaultThrows` asserts that the message contains the format string.
+ PatternKit's miss reason is `"No format handler matched the raw input for normalizer '{name}'."` —
+ the unknown format key is absent. Fixing this without modifying the test requires a custom
+ exception mapping wrapper that adds complexity rather than removing it.
+
+**Resumption condition:** If PatternKit adds a keyed-dispatch variant of `Normalizer` that maps
+string discriminators to handlers (similar to the `TypeDispatchRouter` sketch in `.plan/patternkit-extension-backlog.md`),
+evaluate again.
+
+---
+
+## Deferred: ContentEnricherStep → AsyncContentEnricher
+
+**File:** `src/WorkflowFramework.Extensions.Integration/Transformation/ContentEnricherStep.cs`
+
+**PatternKit primitive:** `PatternKit.Messaging.Transformation.AsyncContentEnricher`
+
+**Why deferred:**
+
+`AsyncContentEnricher` is designed for functional enrichment: each step receives the
+current payload, augments it, and returns a new payload. The step's implementation stores the
+final enriched payload back in the `Message`.
+
+`ContentEnricherStep` is a thin named wrapper around `Func` — a
+side-effecting context mutation. The `IWorkflowContext` is passed by reference; the delegate
+mutates it in place. Adapting to PatternKit would require:
+
+```csharp
+AsyncContentEnricher.Create()
+ .Enrich("enrich", async (ctx, _, ct) => { await enrichAction(ctx); return ctx; })
+ .Build();
+```
+
+The `return ctx` discards PatternKit's copy-returning semantics entirely. The wrapper buys
+nothing — the code after the refactor is longer than the original. The step itself is 3
+non-trivial lines; PatternKit would add ~10 lines of builder setup.
+
+**Resumption condition:** Not warranted at this complexity level. If PatternKit adds a
+side-effect-oriented enricher (`AsyncSideEffectEnricher` that does not require a return value),
+revisit. Otherwise keep bespoke.
+
+---
+
+## Deferred: IdempotentReceiverStep → IdempotentReceiver
+
+**File:** `src/WorkflowFramework.Extensions.Integration/Endpoint/IdempotentReceiverStep.cs`
+
+**PatternKit primitive:** `PatternKit.Messaging.Reliability.IdempotentReceiver`
+
+**Why deferred — behavioral breaking change:**
+
+The bespoke step registers the message ID in the `HashSet` *before* calling the inner
+step. This means: if the inner step throws, a subsequent call with the same ID is *silently
+skipped* (because the ID is already registered). This is tested explicitly:
+
+```
+Test: ReAttemptAfterExceptionIsSkipped
+Pins: "ID was added to the set BEFORE calling inner, so a second call IS skipped
+ even if inner threw."
+```
+
+PatternKit's `IdempotentReceiver` has the opposite semantics:
+
+1. `TryClaimAsync` claims the key (status = `Processing`)
+2. Handler is called
+3. On exception → `MarkFailedAsync` sets status to `Failed`
+
+A subsequent `TryClaimAsync` with the same key in `Failed` status returns `Claimed = false` AND
+sets the status back to `Processing`, allowing the handler to be retried. This is the correct
+resilient behavior for production idempotency, but it breaks the characterization test.
+
+**To unblock:** The test would need to be updated to remove the `ReAttemptAfterExceptionIsSkipped`
+scenario (or relax it to document PatternKit's retry-on-failure semantics). This requires a
+deliberate API-evolution decision — it is not safe to change as a refactor.
+
+---
+
+## Deferred: ClaimCheckStep / ClaimRetrieveStep → ClaimCheck
+
+**File:** `src/WorkflowFramework.Extensions.Integration/Transformation/ClaimCheckStep.cs`
+
+**PatternKit primitive:** `PatternKit.Messaging.Transformation.ClaimCheck`
+
+**Why deferred — interface mismatch:**
+
+The bespoke `IClaimCheckStore` (in `WorkflowFramework.Extensions.Integration.Abstractions`) is
+untyped:
+
+```csharp
+Task StoreAsync(object payload, CancellationToken ct);
+Task