diff --git a/Directory.Build.props b/Directory.Build.props
index 1ce7f560..e3827962 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -40,7 +40,7 @@
Copyright © Vulthil
MIT
https://github.com/Vulthil/Vulthil.SharedKernel
- shared-kernel;ddd;cqrs;result-pattern;dotnet
+
git
https://github.com/Vulthil/Vulthil.SharedKernel
diff --git a/docs/articles/cqrs-pipeline.md b/docs/articles/cqrs-pipeline.md
index 83789c97..a235b1d8 100644
--- a/docs/articles/cqrs-pipeline.md
+++ b/docs/articles/cqrs-pipeline.md
@@ -98,7 +98,7 @@ No additional wiring is needed – the validator is picked up automatically from
### Request Logging
-`RequestLoggingPipelineBehavior` logs the start and completion of every request, attaching the serialised error to the logging scope when the result is a failure.
+`RequestLoggingPipelineBehavior` logs the start and completion of requests whose response type is `Result` (or `Result`), attaching the serialised error to the logging scope when the result is a failure. Requests with any other response type are not covered by this behavior.
### Transactional
diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md
index c1657100..b6dcaacb 100644
--- a/docs/articles/getting-started.md
+++ b/docs/articles/getting-started.md
@@ -12,17 +12,26 @@ The packages are organised into layers so you can adopt only what you need:
| Domain | `Vulthil.Results` | Railway-oriented `Result` for explicit error handling |
| Application | `Vulthil.SharedKernel.Application` | Commands, queries, handlers, and pipeline behaviors |
| Infrastructure | `Vulthil.SharedKernel.Infrastructure` | EF Core base context, generic repository, and outbox processing |
-| Infrastructure | `Vulthil.SharedKernel.Infrastructure.Relational` | Shared relational support and the default relational outbox strategy |
+| Infrastructure | `Vulthil.SharedKernel.Outbox` | EF-free transactional outbox engine (relay, dispatchers, `IOutboxStore`) |
+| Infrastructure | `Vulthil.SharedKernel.Outbox.EntityFrameworkCore` | EF Core implementation of the outbox engine |
+| Infrastructure | `Vulthil.SharedKernel.Infrastructure.Relational` | Shared relational support and the relational outbox store base |
| Infrastructure | `Vulthil.SharedKernel.Infrastructure.Npgsql` | PostgreSQL provider integration (`UseNpgsql`) |
| Infrastructure | `Vulthil.SharedKernel.Infrastructure.MySql` | MySQL provider integration (`UseMySql`) |
| Infrastructure | `Vulthil.SharedKernel.Infrastructure.Cosmos` | Azure Cosmos DB provider integration (`UseCosmosDb`) |
| API | `Vulthil.SharedKernel.Api` | Minimal API endpoint conventions and `Result` → HTTP mapping |
+| Hosting | `Vulthil.Extensions.Hosting` | `IRestartableHostedService` marker for cleanly pausable hosted services |
| Messaging | `Vulthil.Messaging.Abstractions` | Transport-agnostic consumer and publisher contracts |
-| Messaging | `Vulthil.Messaging` | Queue/consumer registration and hosted service orchestration |
+| Messaging | `Vulthil.Messaging` | Queue/consumer registration, hosted orchestration, and the transport SDK |
| Messaging | `Vulthil.Messaging.RabbitMq` | RabbitMQ transport implementation |
+| Messaging | `Vulthil.Messaging.Outbox` | Transactional bus-publish outbox (captures publishes into the outbox) |
+| Messaging | `Vulthil.Messaging.Inbox` | Idempotent-receiver consume filter for exactly/effectively-once processing |
+| Messaging | `Vulthil.Messaging.Inbox.EntityFrameworkCore` | Shared EF Core inbox primitives |
+| Messaging | `Vulthil.Messaging.Inbox.Relational` | Relational idempotency store (transactional exactly-once) |
+| Messaging | `Vulthil.Messaging.Inbox.Cosmos` | Cosmos idempotency store (effectively-once) |
| Testing | `Vulthil.xUnit` | Base test classes, auto-mocking, and Testcontainers integration |
+| Testing | `Vulthil.xUnit.Cosmos` | Cosmos DB emulator fixture for `Vulthil.xUnit` |
| Testing | `Vulthil.Messaging.TestHarness` | In-memory messaging test harness |
-| Testing | `Vulthil.Extensions.Testing` | Shared assertion and setup helpers |
+| Testing | `Vulthil.Extensions.Testing` | Framework-agnostic polling and HTTP response helpers (no xUnit dependency) |
## Minimal Example
diff --git a/docs/articles/inbox-pattern.md b/docs/articles/inbox-pattern.md
index e649c683..d4f41446 100644
--- a/docs/articles/inbox-pattern.md
+++ b/docs/articles/inbox-pattern.md
@@ -4,7 +4,7 @@ Message delivery is **at-least-once**: a broker can redeliver a message (after a
The inbox pattern makes consumers **idempotent**: it records which messages have already been processed and skips duplicates. `Vulthil.Messaging.Inbox` provides this as a consume filter, with the processed-marker written in the **same transaction** as the consumer's business changes — so on a relational store, processing is exactly-once.
-This is an *idempotent receiver*, not a store-and-forward inbox: it rides on top of the transport's existing retry, fault, and dead-letter machinery rather than re-implementing them. Bounding a consumer that keeps failing is therefore **not** the guard's job — on RabbitMQ a poison delivery is retried up to the queue's `MaxRetryCount`, then a `Fault` is published and the message is nacked to the dead-letter exchange. The filter deliberately ignores `IMessageContext.RetryCount` and adds no max-attempts of its own, keeping retry policy in one place (the transport).
+This is an *idempotent receiver*, not a store-and-forward inbox: it rides on top of the transport's existing retry, fault, and dead-letter machinery rather than re-implementing them. Bounding a consumer that keeps failing is therefore **not** the guard's job — on RabbitMQ a persistently-failing delivery is retried per the consumer's [retry policy](messaging.md#retries) (its own registration's, falling back to the queue default), then a `Fault` is published and the delivery is dead-lettered when a dead-letter queue is configured. The filter deliberately ignores `IMessageContext.RetryCount` and adds no max-attempts of its own, keeping retry policy in one place (the transport).
## How It Works
diff --git a/docs/articles/messaging.md b/docs/articles/messaging.md
index 1efa7b67..1e7f066c 100644
--- a/docs/articles/messaging.md
+++ b/docs/articles/messaging.md
@@ -305,17 +305,19 @@ Or in code:
m.ConfigureMessagingOptions(opts => opts.ConsumeFilters.EnableLogging = false);
```
-The filter stays registered in DI; only its behavior is skipped, so it's still
-resolvable in unit tests if you want to assert against it.
+The toggle is applied when `AddMessaging` composes the pipeline: a disabled filter is
+never registered in DI at all (there is no per-delivery flag check), so flipping the
+flag after registration has no effect.
### Idempotent receivers (inbox)
`Vulthil.Messaging.Inbox` ships a consume filter that deduplicates redelivered
-messages, giving exactly-once processing on top of at-least-once delivery. It is a
-transaction-owning filter: the consumer runs inside an `IIdempotencyStore`
-transaction so the processed-marker and the consumer's writes commit atomically.
-Opt a message type in with `AddIdempotentInbox()`. See the
-[Inbox Pattern](inbox-pattern.md) article for the full design.
+messages on top of at-least-once delivery. The consumer invocation is handed to an
+`IIdempotencyStore`, which owns the unit: with a transactional (relational) store the
+processed-marker and the consumer's writes commit atomically — exactly-once
+processing — while a store without cross-partition transactions (e.g. Cosmos) is
+effectively-once. Opt a message type in with `AddIdempotentInbox()`. See
+the [Inbox Pattern](inbox-pattern.md) article for the full design.
## Ordered Processing (per-aggregate)
@@ -523,6 +525,17 @@ await publisher.PublishAsync(new OrderCreatedEvent(orderId), ctx =>
Fault publishing is best-effort: a failure to publish the fault is logged and never prevents the
original delivery from being settled (nacked for dead-lettering).
+### Poison deliveries
+
+Deliveries that fail **before** a consumer can run bypass the retry and fault machinery entirely, and
+no `Fault` is published for them:
+
+- **Unknown message type** (no consumer/plan matches the delivery's type identity): the delivery is
+ **acked and dropped permanently**, with a warning log — it does not reach the dead-letter queue.
+- **Undeserializable body** (malformed JSON, or a body that deserializes to `null`): the delivery is
+ **nacked without requeue**, with an error log. When the queue has a dead-letter queue configured
+ (`q.UseDeadLetterQueue()`), the broker moves it there; otherwise it is discarded.
+
## Routing Keys
Routing keys flow through two distinct configuration sites, one on each side of the wire:
diff --git a/docs/articles/outbox-pattern.md b/docs/articles/outbox-pattern.md
index 67c9422f..505529f8 100644
--- a/docs/articles/outbox-pattern.md
+++ b/docs/articles/outbox-pattern.md
@@ -4,7 +4,7 @@ The transactional outbox pattern guarantees that domain events raised by aggrega
## How It Works
-1. During `SaveChangesAsync`, a `SaveChangesInterceptor` serialises every pending domain event into an `OutboxMessage` row in the same database transaction.
+1. During `SaveChanges`/`SaveChangesAsync` (both the synchronous and asynchronous paths are intercepted), a `SaveChangesInterceptor` serialises every pending domain event into an `OutboxMessage` row in the same database transaction.
2. The aggregate root's event collection is cleared.
3. A background service (`OutboxBackgroundService`) relays unprocessed outbox messages — woken immediately once the captured rows are durable (on transaction commit, or right after a non-transactional `SaveChanges` that captured domain events) for low latency, and polling on an interval as the backstop.
4. Each message is routed by its `OutboxDestination` to the registered `IOutboxDispatcher` that handles it (in-process domain events by default, or the broker — see below).
@@ -12,6 +12,11 @@ The transactional outbox pattern guarantees that domain events raised by aggrega
This guarantees **at-least-once delivery** because the event and the business data are committed atomically.
+Relay order is best-effort, not a contract: messages are fetched oldest-first (`OccurredOnUtc`, then `Id`), but a
+failed message does not block the rest of its batch — the batch keeps dispatching, so later messages overtake the
+failure, which is retried on a later cycle (no head-of-line blocking). With `EnableParallelPublishing`, order
+within a batch is not preserved at all. Consumers that need ordering must not rely on the outbox for it.
+
## Configuration
### Enable outbox processing during DbContext registration
@@ -54,6 +59,11 @@ public sealed class AppDbContext(DbContextOptions options)
`BaseDbContext` owns the `OutboxMessages` `DbSet`; apply the provider-optimized mapping in `OnModelCreating` by calling your provider's extension — `ApplyNpgsqlOutbox()`, `ApplyMySqlOutbox()`, or `ApplyCosmosOutbox()` (as shown above). The agnostic `ApplyOutbox()` is available for custom providers.
+Because the `DbSet` lives on the base class, EF Core's convention discovery puts the `OutboxMessage` entity into
+**every** derived context's model — and its migrations — even when outbox processing is never enabled for that
+context. A context that will never use the outbox can opt out with `modelBuilder.Ignore()` in its
+`OnModelCreating`; an outbox-enabled context must keep the mapping (the relay store refuses to run without it).
+
Only one outbox-enabled `DbContext` is supported per host: the relay and retention background services resolve a
single `IOutboxStore`, so calling `EnableOutboxProcessing()` for a second context throws an
`InvalidOperationException` at startup instead of silently leaving the first context's messages unrelayed. The
@@ -71,7 +81,7 @@ running unprotected.
| `EnableParallelPublishing` | `false` | Publish messages in parallel within a batch (each dispatch runs in its own DI scope) |
| `MaxDegreeOfParallelism` | 4 | Maximum concurrent dispatches when `EnableParallelPublishing` is enabled |
| `OutboxProcessingDelaySeconds` | 2 | Base polling delay between processing cycles |
-| `MaxDelaySeconds` | 60 | Maximum back-off delay when no messages are found |
+| `MaxDelaySeconds` | 60 | Maximum back-off delay when a cycle relays nothing (no pending messages, or every fetched message failed) |
| `EnableTracing` | `true` | Carry the originating trace identifier when publishing |
## Observability
@@ -105,7 +115,7 @@ Processed and dead-lettered rows remain in the `OutboxMessages` table after rela
| `SweepInterval` | 1 hour | Delay between sweeps |
| `BatchSize` | 1000 | Rows deleted per batch within a sweep |
-The sweep deletes rows whose `ProcessedOnUtc` **or** `FailedOnUtc` is older than `RetentionPeriod`; **pending rows are never touched**. It runs through the registered `IOutboxStore` when it implements `IOutboxRetentionStore` (the EF Core store does) — every store deletes at most `BatchSize` rows per call (relational providers select the oldest eligible keys and delete them set-based with `ExecuteDelete`), so a large backlog is drained in bounded, short-lived batches, and the **same sweep covers Cosmos** (a Cosmos container TTL is not used, because it cannot tell a pending row from a relayed one and could expire an undelivered message).
+The sweep deletes rows whose `ProcessedOnUtc` **or** `FailedOnUtc` is older than `RetentionPeriod`; **pending rows are never touched**. It runs through the registered `IOutboxStore` when it implements `IOutboxRetentionStore` (the EF Core store does; a custom store that doesn't gets a one-time warning and the sweep is skipped) — every store deletes at most `BatchSize` rows per call (relational providers select the oldest eligible keys and delete them set-based with `ExecuteDelete`), so a large backlog is drained in bounded, short-lived batches, and the **same sweep covers Cosmos** (a Cosmos container TTL is not used, because it cannot tell a pending row from a relayed one and could expire an undelivered message).
## Custom Outbox Store
@@ -156,6 +166,13 @@ Capture is relational-only (it enlists in the ambient transaction); the relay wo
the general publish/send **filter pipeline** (`IPublishFilter`, registered via `AddPublishFilter()`), which is the
publish-side counterpart to consume filters and can host other cross-cutting concerns.
+Two pipeline consequences to know: capturing **short-circuits** the pipeline, so publish filters registered after
+`AddTransactionalOutbox()` never see a captured message; and the relay publishes captured messages straight through
+the transport, **bypassing the publish-filter pipeline entirely** (nothing runs twice, but nothing else observes the
+relayed publish either). Register any filter that must observe every outgoing message *before*
+`AddTransactionalOutbox()`. Request/reply is unaffected — requests never flow through the publish filters and are
+never captured (a request awaiting a synchronous reply cannot be deferred to a relay).
+
The capture preserves the publish faithfully: the message id, correlation id, routing key, send destination, custom
headers, and the current trace context (`TraceParent`/`TraceState`, when tracing is enabled) are stored with the row
and re-applied on relay — the relayed publish runs under an activity linked to the capturing trace. Headers persist
diff --git a/docs/articles/packages/index.md b/docs/articles/packages/index.md
index 47a8083c..9a94240e 100644
--- a/docs/articles/packages/index.md
+++ b/docs/articles/packages/index.md
@@ -14,7 +14,9 @@ Focused usage guidance for each package in `src`. Pick the page that matches the
## Infrastructure
- [Vulthil.SharedKernel.Infrastructure](vulthil-sharedkernel-infrastructure.md) – `BaseDbContext`, generic repository, and outbox composition entry point.
-- [Vulthil.SharedKernel.Infrastructure.Relational](vulthil-sharedkernel-infrastructure-relational.md) – relational outbox strategy and `MigrateAsync` for EF Core providers.
+- [Vulthil.SharedKernel.Outbox](vulthil-sharedkernel-outbox.md) – the EF-free transactional outbox engine (relay, dispatchers, `IOutboxStore` seam).
+- [Vulthil.SharedKernel.Outbox.EntityFrameworkCore](vulthil-sharedkernel-outbox-entityframeworkcore.md) – the EF Core implementation of the outbox engine.
+- [Vulthil.SharedKernel.Infrastructure.Relational](vulthil-sharedkernel-infrastructure-relational.md) – relational outbox store base and `MigrateAsync` for EF Core providers.
- [Vulthil.SharedKernel.Infrastructure.Npgsql](vulthil-sharedkernel-infrastructure-npgsql.md) – `UseNpgsql` provider wiring with PostgreSQL-tuned outbox.
- [Vulthil.SharedKernel.Infrastructure.MySql](vulthil-sharedkernel-infrastructure-mysql.md) – `UseMySql` provider wiring with MySQL-tuned outbox.
- [Vulthil.SharedKernel.Infrastructure.Cosmos](vulthil-sharedkernel-infrastructure-cosmos.md) – `UseCosmosDb` provider wiring with Cosmos-specific outbox.
@@ -23,14 +25,24 @@ Focused usage guidance for each package in `src`. Pick the page that matches the
- [Vulthil.SharedKernel.Api](vulthil-sharedkernel-api.md) – minimal API endpoint conventions and `Result` → HTTP mapping.
+## Hosting
+
+- [Vulthil.Extensions.Hosting](vulthil-extensions-hosting.md) – `IRestartableHostedService`, the marker for hosted services that can be paused and resumed cleanly.
+
## Messaging
- [Vulthil.Messaging.Abstractions](vulthil-messaging-abstractions.md) – transport-agnostic consumer and publisher contracts.
-- [Vulthil.Messaging](vulthil-messaging.md) – queue/consumer registration and hosted processing.
+- [Vulthil.Messaging](vulthil-messaging.md) – queue/consumer registration, hosted processing, and the transport-author SDK.
- [Vulthil.Messaging.RabbitMq](vulthil-messaging-rabbitmq.md) – RabbitMQ transport implementation.
+- [Vulthil.Messaging.Outbox](vulthil-messaging-outbox.md) – transactional bus-publish outbox bridging the outbox engine to the broker.
+- [Vulthil.Messaging.Inbox](vulthil-messaging-inbox.md) – idempotent-receiver consume filter (`IIdempotencyStore` contract).
+- [Vulthil.Messaging.Inbox.EntityFrameworkCore](vulthil-messaging-inbox-entityframeworkcore.md) – shared EF Core inbox primitives (`InboxMessage`, `ISaveInboxMessages`).
+- [Vulthil.Messaging.Inbox.Relational](vulthil-messaging-inbox-relational.md) – relational idempotency store (transactional exactly-once).
+- [Vulthil.Messaging.Inbox.Cosmos](vulthil-messaging-inbox-cosmos.md) – Cosmos idempotency store (effectively-once).
## Testing
-- [Vulthil.xUnit](vulthil-xunit.md) – reusable xUnit base classes and auto-mocking.
+- [Vulthil.xUnit](vulthil-xunit.md) – reusable xUnit base classes, auto-mocking, containers, and HTTP mocks.
+- [Vulthil.xUnit.Cosmos](vulthil-xunit-cosmos.md) – Cosmos DB emulator fixture for `Vulthil.xUnit`.
- [Vulthil.Messaging.TestHarness](vulthil-messaging-testharness.md) – in-memory messaging test harness.
-- [Vulthil.Extensions.Testing](vulthil-extensions-testing.md) – shared assertion and setup helpers.
+- [Vulthil.Extensions.Testing](vulthil-extensions-testing.md) – framework-agnostic polling and HTTP response helpers.
diff --git a/docs/articles/packages/toc.yml b/docs/articles/packages/toc.yml
index dbca2a39..67e4b48f 100644
--- a/docs/articles/packages/toc.yml
+++ b/docs/articles/packages/toc.yml
@@ -22,6 +22,8 @@
href: vulthil-sharedkernel-infrastructure-cosmos.md
- name: Vulthil.SharedKernel.Api
href: vulthil-sharedkernel-api.md
+- name: Vulthil.Extensions.Hosting
+ href: vulthil-extensions-hosting.md
- name: Vulthil.Messaging.Abstractions
href: vulthil-messaging-abstractions.md
- name: Vulthil.Messaging
diff --git a/docs/articles/packages/vulthil-extensions-hosting.md b/docs/articles/packages/vulthil-extensions-hosting.md
new file mode 100644
index 00000000..7cceb287
--- /dev/null
+++ b/docs/articles/packages/vulthil-extensions-hosting.md
@@ -0,0 +1,39 @@
+# Vulthil.Extensions.Hosting
+
+Small hosting abstractions for Vulthil. The package currently contains one contract:
+`IRestartableHostedService`.
+
+## When to use
+
+- A hosted service (such as a database-polling relay) should be pausable around operations that must not run
+ concurrently with it
+- Test infrastructure needs to stop background work around a reset and resume it afterwards
+
+## `IRestartableHostedService`
+
+A marker interface an `IHostedService` implements to declare that its execution can be **stopped and started
+again cleanly**. Infrastructure can then pause the service for the duration of an operation that must not run
+concurrently with it, and resume it afterwards.
+
+The primary use is test isolation: `Vulthil.xUnit` stops every registered hosted service implementing this
+marker around the per-test database reset and restarts it afterwards, so a live database-polling relay (for
+example the `Vulthil.SharedKernel.Outbox` background relay) does not contend with the reset and time it out.
+The relay opts in simply by implementing the marker — no test-only code, and the testing library never depends
+on the outbox engine.
+
+```csharp
+internal sealed class OutboxBackgroundService(/* ... */) : IRestartableHostedService, IDisposable
+{
+ // StartAsync/StopAsync manage the execute task so a Stop/Start cycle re-runs it cleanly.
+}
+```
+
+## Implementation guidance
+
+Implement the marker only on services whose `StartAsync`/`StopAsync` are idempotent across repeated cycles.
+Prefer implementing `IHostedService` directly over inheriting `BackgroundService`: the host observes only the
+execute task of a `BackgroundService`'s first start and stops the whole host when that task is canceled while
+the application is running — and on .NET 10 a stop racing service startup can cancel the task before
+`ExecuteAsync` has run at all.
+
+See [Testing](../testing.md) for how the marker participates in the per-test reset.
diff --git a/docs/articles/packages/vulthil-extensions-testing.md b/docs/articles/packages/vulthil-extensions-testing.md
index afa0fe46..71e5b2dc 100644
--- a/docs/articles/packages/vulthil-extensions-testing.md
+++ b/docs/articles/packages/vulthil-extensions-testing.md
@@ -1,17 +1,18 @@
# Vulthil.Extensions.Testing
-Use `Vulthil.Extensions.Testing` for shared testing helpers and extension points.
+Use `Vulthil.Extensions.Testing` for framework-agnostic test helpers. It has no xUnit dependency — the
+xUnit-coupled test stack (base classes, fixtures, containers) lives in [`Vulthil.xUnit`](vulthil-xunit.md).
## When to use
-- Reusing assertion and setup helpers across test suites
-- Reducing repeated test composition boilerplate
-- Polling asynchronous conditions during integration tests
+- Polling asynchronous conditions during integration tests (`Polling.WaitAsync`)
+- Reading and asserting JSON HTTP responses (`GetResponseAsync`)
+- Any test framework — nothing here requires xUnit
## Pattern
+- Express the polled condition as a `Result`/`Result` so each failed attempt carries a diagnosable `Error`
- Keep helpers small and composable
-- Prefer intent-revealing test extensions
- Avoid embedding production logic in test helpers
## Polling
@@ -36,3 +37,12 @@ result.IsSuccess.ShouldBeTrue();
```
When polling times out, `PollingResult.PollingError` exposes the individual errors collected from each failed attempt.
+
+## HTTP responses
+
+`GetResponseAsync` asserts an `HttpResponseMessage` indicates success and deserializes its JSON body:
+
+```csharp
+var response = await client.GetAsync("/weather/london");
+var forecast = await response.GetResponseAsync();
+```
diff --git a/docs/articles/packages/vulthil-messaging-inbox-cosmos.md b/docs/articles/packages/vulthil-messaging-inbox-cosmos.md
index e332565d..a6554905 100644
--- a/docs/articles/packages/vulthil-messaging-inbox-cosmos.md
+++ b/docs/articles/packages/vulthil-messaging-inbox-cosmos.md
@@ -33,5 +33,5 @@ public sealed class AppDbContext(DbContextOptions options) : DbCon
builder.Services.AddCosmosInbox();
```
-See the [Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md)
+See the [Inbox Pattern](../inbox-pattern.md)
article for how the guarantees differ from the relational store.
diff --git a/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md b/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md
index 5459c4e8..20add03f 100644
--- a/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md
+++ b/docs/articles/packages/vulthil-messaging-inbox-entityframeworkcore.md
@@ -25,5 +25,5 @@ public sealed class AppDbContext(DbContextOptions options) : DbCon
}
```
-See the [Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md)
+See the [Inbox Pattern](../inbox-pattern.md)
article for the full design.
diff --git a/docs/articles/packages/vulthil-messaging-inbox-relational.md b/docs/articles/packages/vulthil-messaging-inbox-relational.md
index 20556312..a710c9dd 100644
--- a/docs/articles/packages/vulthil-messaging-inbox-relational.md
+++ b/docs/articles/packages/vulthil-messaging-inbox-relational.md
@@ -36,5 +36,5 @@ builder.Services.AddRelationalInbox();
The consumer and the store must resolve the same scoped `DbContext` instance (the default with `AddDbContext`).
Add an EF Core migration for the `InboxMessage` table as you would for any entity. See the
-[Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md) article
+[Inbox Pattern](../inbox-pattern.md) article
for details.
diff --git a/docs/articles/packages/vulthil-messaging-inbox.md b/docs/articles/packages/vulthil-messaging-inbox.md
index 9a0a3809..aec71d47 100644
--- a/docs/articles/packages/vulthil-messaging-inbox.md
+++ b/docs/articles/packages/vulthil-messaging-inbox.md
@@ -35,5 +35,5 @@ builder.AddMessaging(messaging =>
});
```
-See the [Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md)
+See the [Inbox Pattern](../inbox-pattern.md)
article for the design, guarantees, and the message-id stability contract.
diff --git a/docs/articles/packages/vulthil-messaging-outbox.md b/docs/articles/packages/vulthil-messaging-outbox.md
index 3f630082..da31580f 100644
--- a/docs/articles/packages/vulthil-messaging-outbox.md
+++ b/docs/articles/packages/vulthil-messaging-outbox.md
@@ -37,5 +37,5 @@ builder.AddMessaging(messaging =>
The application's `DbContext` must implement `ISaveOutboxMessages` (a `BaseDbContext` already does). Capture is
relational-only (it enlists in the ambient transaction); the relay works on any provider.
-See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md)
+See the [Outbox Pattern](../outbox-pattern.md)
article for the design, the pluggable-sink model, and the commit-time trigger.
diff --git a/docs/articles/packages/vulthil-messaging-testharness.md b/docs/articles/packages/vulthil-messaging-testharness.md
index e9fe173b..9122377b 100644
--- a/docs/articles/packages/vulthil-messaging-testharness.md
+++ b/docs/articles/packages/vulthil-messaging-testharness.md
@@ -2,7 +2,8 @@
An in-memory messaging transport for tests. It runs your consumers with no broker and captures every produced
and consumed message for assertion. Built entirely on the public `Vulthil.Messaging.Transport` SDK, so it
-mirrors the real consumer topology assembled from your queue configuration.
+assembles the same execution plans (consumers, polymorphic dispatch, per-consumer retry resolution) from your
+queue configuration that a real transport would.
## When to use
@@ -13,7 +14,10 @@ mirrors the real consumer topology assembled from your queue configuration.
## Pattern
- Dispatch is synchronous: when a publish/send/request call returns, every consumer it triggered has run — no polling
-- A one-way consumer's exception propagates to the publisher/sender; a request consumer's exception becomes a failed result
+- A one-way consumer's exception does **not** propagate to the publisher/sender: the consumer is retried per its
+ resolved retry policy (attempts run back-to-back, without the configured delays), then a `Fault` is published
+ and captured — assert it via `Published>()`. A request consumer's exception becomes a failed
+ `Result` on the requesting side
- Keep assertions on `ITestHarness` deterministic and explicit; `Clear()` between phases
## Usage
@@ -57,5 +61,4 @@ transport with the harness, leaving production code untouched:
builder.ConfigureServices(services => services.ReplaceTransportWithTestHarness());
```
-See the [Testing guide](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/testing.md) for
-the full API and details.
+See the [Testing guide](../testing.md#messaging-test-harness) for the full API and details.
diff --git a/docs/articles/packages/vulthil-messaging.md b/docs/articles/packages/vulthil-messaging.md
index 854979d0..60a7e869 100644
--- a/docs/articles/packages/vulthil-messaging.md
+++ b/docs/articles/packages/vulthil-messaging.md
@@ -48,15 +48,21 @@ messaging.ConfigureMessage(message =>
});
```
-### Per-consumer routing overrides
+### Per-consumer retry override
+
+`AddConsumer` accepts a configurator; its knob is the consumer's retry policy, which overrides the queue-level
+default for that consumer alone:
```csharp
queue.AddConsumer(c =>
{
- c.Bind("order.eu");
+ c.UseRetry(r => r.Immediate(5));
});
```
+Binding patterns are configured per queue, not per consumer — use
+`queue.Subscribe("order.eu")` (see [Messaging — Routing Keys](../messaging.md#routing-keys)).
+
### Configuration-driven setup
Queue and message settings under `Messaging:Queues:*` and `Messaging:Messages:*`
diff --git a/docs/articles/packages/vulthil-sharedkernel-api.md b/docs/articles/packages/vulthil-sharedkernel-api.md
index 4d870f5d..990328cc 100644
--- a/docs/articles/packages/vulthil-sharedkernel-api.md
+++ b/docs/articles/packages/vulthil-sharedkernel-api.md
@@ -31,7 +31,7 @@ public sealed class GetUserEndpoint : IEndpoint
}
```
-`ToIResult()` returns `Results, ValidationProblem, NotFound, Conflict, ProblemHttpResult>`, so OpenAPI automatically documents all possible response types.
+`ToIResult()` returns `Results, ValidationProblem, NotFound, Conflict, ProblemHttpResult>`, so OpenAPI automatically documents all possible response types. At runtime every failure — including `NotFound` and `Conflict` — is returned as a problem response (RFC 7807) with the status mapped from the `ErrorType` and the error's description as `detail`; the `NotFound`/`Conflict` union members exist for the OpenAPI documentation. See [Result Pattern — Mapping to HTTP Responses](../result-pattern.md#mapping-to-http-responses).
### Registration and mapping
diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md
index 45262b0d..b82d3175 100644
--- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md
+++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-cosmos.md
@@ -6,11 +6,11 @@ Use `Vulthil.SharedKernel.Infrastructure.Cosmos` to run the shared infrastructur
- Cosmos DB is the underlying store for an application's primary `DbContext`
- Aspire-wired Cosmos connection via `AddCosmosDbContext`
-- Outbox processing should use the Cosmos-specific strategy (best-effort relay without relational locking)
+- Outbox processing should use the Cosmos-specific store (best-effort relay without relational locking)
## Pattern
-- Call `UseCosmosDb("connectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Cosmos outbox strategy
+- Call `UseCosmosDb("connectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Cosmos outbox store
- Configure the Cosmos-specific entity model for `OutboxMessage` via the `ApplyCosmosOutbox()` model-builder extension (call it from your `OnModelCreating`)
- Order between `UseCosmosDb`, `EnableOutboxProcessing`, and `UseOutboxStore` does not matter; the configurator defers the underlying registrations until the full chain has executed, and the Cosmos outbox store is applied only as a default – a custom store selected via `UseOutboxStore` is always preserved
diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md
index fa322e41..2e3eaaaa 100644
--- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md
+++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-mysql.md
@@ -1,15 +1,15 @@
# Vulthil.SharedKernel.Infrastructure.MySql
-Use `Vulthil.SharedKernel.Infrastructure.MySql` to wire a MySQL-backed `DbContext` together with the MySQL-tuned outbox strategy.
+Use `Vulthil.SharedKernel.Infrastructure.MySql` to wire a MySQL-backed `DbContext` together with the MySQL-tuned outbox store.
## When to use
- MySQL is the underlying database for an application's primary `DbContext`
-- Outbox processing should use the MySQL strategy (row-level locking via `FOR UPDATE SKIP LOCKED`)
+- Outbox processing should use the MySQL store (row-level locking via `FOR UPDATE SKIP LOCKED`)
## Pattern
-- Call `UseMySql("ConnectionStringKey")` on the database infrastructure configurator – it registers a pooled EF Core context for the named connection string, selects the MySQL outbox strategy, and (when outbox processing is enabled) wires the commit-time relay trigger
+- Call `UseMySql("ConnectionStringKey")` on the database infrastructure configurator – it registers a pooled EF Core context for the named connection string, selects the MySQL outbox store, and (when outbox processing is enabled) wires the commit-time relay trigger
- Order between `UseMySql`, `EnableOutboxProcessing`, and `UseOutboxStore` does not matter; the configurator defers the underlying registrations until the full chain has executed, and the MySQL outbox store is applied only as a default – a custom store selected via `UseOutboxStore` is always preserved
- The relay's locking fetch SQL is composed from the model's mapped identifiers, so custom outbox table or column names (a naming convention, `ToTable`, or `HasColumnName`) are supported, and the relay dispatches strictly in `(OccurredOnUtc, Id)` order
- Retrying execution strategies are fully supported – the outbox processor runs its transactional unit inside the context's execution strategy (`Database.CreateExecutionStrategy().ExecuteAsync`)
diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md
index 7ef395a0..5a55ca40 100644
--- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md
+++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-npgsql.md
@@ -1,16 +1,16 @@
# Vulthil.SharedKernel.Infrastructure.Npgsql
-Use `Vulthil.SharedKernel.Infrastructure.Npgsql` to wire a PostgreSQL-backed `DbContext` together with the PostgreSQL-tuned outbox strategy.
+Use `Vulthil.SharedKernel.Infrastructure.Npgsql` to wire a PostgreSQL-backed `DbContext` together with the PostgreSQL-tuned outbox store.
## When to use
- PostgreSQL is the underlying database for an application's primary `DbContext`
- Aspire-wired connection strings via `AddNpgsqlDbContext`
-- Outbox processing should use the PostgreSQL strategy (row-level locking via `FOR UPDATE SKIP LOCKED`)
+- Outbox processing should use the PostgreSQL store (row-level locking via `FOR UPDATE SKIP LOCKED`)
## Pattern
-- Call `UseNpgsql("ConnectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Npgsql outbox strategy
+- Call `UseNpgsql("ConnectionStringKey")` on the database infrastructure configurator – it both registers the EF Core context and selects the Npgsql outbox store
- Order between `UseNpgsql`, `EnableOutboxProcessing`, and `UseOutboxStore` does not matter; the configurator defers the underlying registrations until the full chain has executed, and the Npgsql outbox store is applied only as a default – a custom store selected via `UseOutboxStore` is always preserved
- The relay's locking fetch SQL is composed from the model's mapped identifiers, so custom outbox table or column names (a naming convention such as `UseSnakeCaseNamingConvention`, `ToTable`, or `HasColumnName`) are supported, and the relay dispatches strictly in `(OccurredOnUtc, Id)` order
- Retrying execution strategies are fully supported – the outbox processor runs its transactional unit inside the context's execution strategy (`Database.CreateExecutionStrategy().ExecuteAsync`), so there is no need to force `DisableRetry`
diff --git a/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md b/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md
index b7ed06de..62994add 100644
--- a/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md
+++ b/docs/articles/packages/vulthil-sharedkernel-infrastructure-relational.md
@@ -1,6 +1,6 @@
# Vulthil.SharedKernel.Infrastructure.Relational
-Use `Vulthil.SharedKernel.Infrastructure.Relational` as the shared base for relational EF Core providers and as the source of the default relational outbox strategy.
+Use `Vulthil.SharedKernel.Infrastructure.Relational` as the shared base for relational EF Core providers: it holds the relational outbox store base class, the commit-time relay trigger, and the `MigrateAsync` startup helper.
## When to use
@@ -10,7 +10,7 @@ Use `Vulthil.SharedKernel.Infrastructure.Relational` as the shared base for rela
## Pattern
- Pull this package in transitively via the provider package (`Vulthil.SharedKernel.Infrastructure.Npgsql`, `…MySql`, etc.); it is rarely referenced directly from application code
-- Derive provider-specific outbox strategies from `RelationalOutboxStrategy` to add row-level locking or other provider-specific tuning
+- Derive provider-specific outbox stores from `RelationalOutboxStore` to add row-level locking or other provider-specific tuning
- Apply migrations during startup rather than at build time so deployments stay reproducible
## Usage
@@ -29,21 +29,27 @@ app.Run();
`MigrateAsync` checks for pending migrations and only invokes `Database.MigrateAsync()` when at least one is pending, so it is safe to call on every startup.
-### Reusing the relational outbox strategy
+### Reusing the relational outbox store
-Provider packages wire `RelationalOutboxStrategy` (or a subclass of it) automatically through their own `Use*` extension. If you are authoring a new provider, inherit from `RelationalOutboxStrategy` and override `FetchMessagesAsync` to add provider-specific row locking:
+Provider packages wire `RelationalOutboxStore` (or a subclass of it) automatically through their own `Use*` extension. The base class records relay outcomes with set-based `ExecuteUpdate` calls, deletes retention batches by key, and requires the relay batch to run inside a transaction — it throws at relay time if `TContext` does not implement `IUnitOfWork` (derive from `BaseDbContext`), because without a transaction provider row-locking such as `FOR UPDATE SKIP LOCKED` would release immediately after the fetch and concurrent relay instances could double-dispatch.
+
+If you are authoring a new provider, inherit from `RelationalOutboxStore` and override the fetch to add provider-specific row locking:
```csharp
-public sealed class MyProviderOutboxStrategy : RelationalOutboxStrategy
+public sealed class MyProviderOutboxStore(
+ TContext dbContext, TimeProvider timeProvider, IOptions options)
+ : RelationalOutboxStore(dbContext, timeProvider, options)
+ where TContext : DbContext, ISaveOutboxMessages
{
- public override Task> FetchMessagesAsync(
- DbSet outboxMessages,
- int batchSize,
- int maxRetries,
- CancellationToken cancellationToken)
+ protected override Task> FetchMessagesAsync(
+ int batchSize, int maxRetries, CancellationToken cancellationToken)
{
// Add provider-specific FOR UPDATE SKIP LOCKED or equivalent here.
- return base.FetchMessagesAsync(outboxMessages, batchSize, maxRetries, cancellationToken);
+ return base.FetchMessagesAsync(batchSize, maxRetries, cancellationToken);
}
}
```
+
+### Commit-time relay trigger
+
+`AddRelationalOutboxCommitTrigger()` registers a transaction interceptor (`OutboxCommitInterceptor`) that wakes the outbox relay when an explicit database transaction commits, so captured messages relay promptly instead of waiting for the next poll. Provider `Use*` extensions register it automatically when outbox processing is enabled.
diff --git a/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md b/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md
index 9a8e28aa..8738144e 100644
--- a/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md
+++ b/docs/articles/packages/vulthil-sharedkernel-outbox-entityframeworkcore.md
@@ -19,5 +19,5 @@ engine. It isolates all EF Core coupling so the engine package stays persistence
- `DomainEventsToOutboxMessageSaveChangesInterceptor` / `IOutboxInterceptor` — domain-event capture.
- `ApplyOutbox()` — a `ModelBuilder` extension applying the provider-agnostic `OutboxMessage` mapping. Provider packages offer optimized alternatives (`ApplyNpgsqlOutbox()`, `ApplyMySqlOutbox()`, `ApplyCosmosOutbox()`).
-See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md)
+See the [Outbox Pattern](../outbox-pattern.md)
article for the design.
diff --git a/docs/articles/packages/vulthil-sharedkernel-outbox.md b/docs/articles/packages/vulthil-sharedkernel-outbox.md
index 43ad7470..fe41e6c1 100644
--- a/docs/articles/packages/vulthil-sharedkernel-outbox.md
+++ b/docs/articles/packages/vulthil-sharedkernel-outbox.md
@@ -27,5 +27,5 @@ seam. It has **no EF Core dependency**; the EF implementation lives in
- Opt into a retention sweep via `EnableOutboxProcessing(o => o.Retention.Enabled = true)` to periodically delete processed and dead-lettered rows
older than a window (relational set-based `ExecuteDelete`; the same sweep covers Cosmos).
-See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md)
+See the [Outbox Pattern](../outbox-pattern.md)
article for the design, the pluggable-sink model, and the commit-time trigger.
diff --git a/docs/articles/packages/vulthil-sharedkernel.md b/docs/articles/packages/vulthil-sharedkernel.md
index 99e5992b..d0ddae19 100644
--- a/docs/articles/packages/vulthil-sharedkernel.md
+++ b/docs/articles/packages/vulthil-sharedkernel.md
@@ -17,11 +17,11 @@ Use `Vulthil.SharedKernel` for domain primitives reused across bounded contexts.
### Defining an entity
+Entities compare by identity, so the identifier type must have value equality — use a `record` (or
+`readonly record struct`), not a plain class:
+
```csharp
-public sealed class UserId(Guid value)
-{
- public Guid Value { get; } = value;
-}
+public sealed record UserId(Guid Value);
public sealed class User : AggregateRoot
{
diff --git a/docs/articles/packages/vulthil-xunit.md b/docs/articles/packages/vulthil-xunit.md
index 73022c11..e5f21f6f 100644
--- a/docs/articles/packages/vulthil-xunit.md
+++ b/docs/articles/packages/vulthil-xunit.md
@@ -12,3 +12,8 @@ Use `Vulthil.xUnit` for reusable xUnit test base infrastructure.
- Derive from shared base test classes consistently
- Keep fixture setup centralized and reusable
- Keep test classes focused on behavior, not plumbing
+
+This package is xUnit-coupled by design; the framework-agnostic helpers (`Result`-based polling, HTTP response
+deserialization) live in [Vulthil.Extensions.Testing](vulthil-extensions-testing.md).
+
+See [Testing](../testing.md) for the full guide (base classes, containers, `ContainerHost`, HTTP mocks).
diff --git a/docs/articles/result-pattern.md b/docs/articles/result-pattern.md
index f6400403..d17fea6d 100644
--- a/docs/articles/result-pattern.md
+++ b/docs/articles/result-pattern.md
@@ -162,3 +162,14 @@ public async Task Get(Guid id)
return result.ToActionResult(this);
}
```
+
+Success maps to `200 Ok` (`Result`), `204 NoContent` (non-generic `Result`), or `201 CreatedAtRoute`
+via `ToCreatedAtRouteHttpResult`. Every failure returns an RFC 7807 problem body with the status from the
+[table above](#error-classifications): `detail` carries the error's `Description`, and the error's `Code`
+appears as a key in the problem `extensions` (mapped to the description). `Validation` failures return a
+validation problem whose per-field `errors` dictionary is built from the `ValidationError`'s inner errors. The
+minimal-API and MVC paths produce the same status and detail for the same error.
+
+One nuance of the typed union: at runtime `NotFound` and `Conflict` errors surface as a `ProblemHttpResult`
+carrying 404/409 **with that problem body** — the `NotFound`/`Conflict` members of the union exist so OpenAPI
+documents those status codes, not as the results actually returned.
diff --git a/docs/articles/testing.md b/docs/articles/testing.md
index 1aeb4766..eb40cb95 100644
--- a/docs/articles/testing.md
+++ b/docs/articles/testing.md
@@ -9,7 +9,7 @@
| `Vulthil.xUnit` | Base test classes, auto-mocking, `WebApplicationFactory` support, and Testcontainers integration |
| `Vulthil.xUnit.Cosmos` | Azure Cosmos DB emulator fixture with a database per test class |
| `Vulthil.Messaging.TestHarness` | In-memory messaging transport for asserting published/consumed messages |
-| `Vulthil.Extensions.Testing` | Shared assertion helpers and test composition utilities |
+| `Vulthil.Extensions.Testing` | Framework-agnostic helpers — `Result`-based polling and HTTP response deserialization; no xUnit dependency |
## Unit Tests
@@ -94,7 +94,7 @@ Use `IClassFixture` so each test class gets its own factory. By d
Key features:
- **Scoped services** – `ScopedServices` gives you a fresh DI scope per test.
-- **Automatic database reset** – the database is reset with Respawn after each test, so tests sharing a factory start from a clean state.
+- **Automatic database reset** – the database is reset with Respawn after each test, so tests sharing a factory start from a clean state. Hosted services implementing `IRestartableHostedService` (from `Vulthil.Extensions.Hosting`) are stopped around the reset and restarted afterwards, so a database-polling relay such as the outbox background service never contends with it.
- **Log capture** – application logs are routed to the currently running test automatically (via `TestContext`). The `ITestOutputHelper` constructor parameter is for writing test output directly.
- **One host per class** – all tests in a class run against the fixture's test host. Override `CreateFactory()` (e.g. `FactoryFixture.WithWebHostBuilder(...)`) when a class needs per-test host configuration; derived factories are disposed after each test.
@@ -248,9 +248,16 @@ Mock state is reset after each test (like the database), so stubs and captured r
`Vulthil.Messaging.TestHarness` provides an in-memory transport that runs your consumers with no broker and
captures every produced and consumed message for assertion. It is built entirely on the public
-`Vulthil.Messaging.Transport` SDK, so it mirrors the real consumer topology assembled from your queue
-configuration. Dispatch is synchronous — by the time a publish/send/request call returns, every consumer (and
-stub) it triggered has run, so assertions need no polling.
+`Vulthil.Messaging.Transport` SDK, so it assembles the same execution plans — consumers, polymorphic dispatch,
+per-consumer retry resolution — from your queue configuration that a real transport would. Dispatch is
+synchronous — by the time a publish/send/request call returns, every consumer (and stub) it triggered has run,
+so assertions need no polling.
+
+Two fidelity limits to keep in mind: the harness dispatches each produced message **once** to all matching
+consumers (a real broker delivers a distinct copy per subscribed queue), and partition lanes are not simulated
+(dispatch is inline and ordered by call). Retries run back-to-back without the configured delays; a one-way
+consumer that exhausts them publishes a `Fault` — observable via `Published>()` — while the
+publish call itself completes normally.
### Composing a harness (unit/component tests)
diff --git a/src/Vulthil.Extensions.Hosting/README.md b/src/Vulthil.Extensions.Hosting/README.md
index 761ce739..eb0db275 100644
--- a/src/Vulthil.Extensions.Hosting/README.md
+++ b/src/Vulthil.Extensions.Hosting/README.md
@@ -26,3 +26,7 @@ Implement it only on services whose `StartAsync`/`StopAsync` are idempotent acro
implementing `IHostedService` directly over inheriting `BackgroundService`: the host observes only the execute task
of a `BackgroundService`'s first start and stops the whole host when that task is canceled while the application is
running — and on .NET 10 a stop racing service startup can cancel the task before `ExecuteAsync` has run at all.
+
+## Docs
+
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj b/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj
index 0952ae28..d5613fb4 100644
--- a/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj
+++ b/src/Vulthil.Extensions.Hosting/Vulthil.Extensions.Hosting.csproj
@@ -2,6 +2,7 @@
Hosting abstractions for Vulthil — notably IRestartableHostedService, a marker a background service implements to declare its execution can be stopped and started again cleanly, so infrastructure (such as test harnesses) may safely pause and resume it.
+ hosting;hosted-service;ihostedservice;background-service;restartable;lifecycle
diff --git a/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs b/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs
index 326f1499..f9f24250 100644
--- a/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs
+++ b/src/Vulthil.Extensions.Testing/HttpResponseMessageExtensions.cs
@@ -8,12 +8,14 @@ namespace Vulthil.Extensions.Testing;
public static class HttpResponseMessageExtensions
{
///
- /// Reads the JSON content of the HTTP response and deserializes it into the specified type.
+ /// Asserts the response indicates success, then reads its JSON content and deserializes it into
+ /// . Throws when the response is , has a
+ /// non-success status code, or its body is empty or deserializes to .
///
- ///
- ///
- ///
- ///
+ /// The type to deserialize the JSON response body into.
+ /// The HTTP response to read.
+ /// A token to observe for cancellation.
+ /// The deserialized response body.
public static async Task GetResponseAsync(this HttpResponseMessage? response, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(response);
diff --git a/src/Vulthil.Extensions.Testing/README.md b/src/Vulthil.Extensions.Testing/README.md
index 61dd8c52..d8e2664e 100644
--- a/src/Vulthil.Extensions.Testing/README.md
+++ b/src/Vulthil.Extensions.Testing/README.md
@@ -10,4 +10,4 @@ Testing-oriented extensions for asserting and composing application behaviors.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj b/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj
index ee85fda2..d5064944 100644
--- a/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj
+++ b/src/Vulthil.Extensions.Testing/Vulthil.Extensions.Testing.csproj
@@ -1,7 +1,8 @@
- Testing-oriented extensions for asserting and composing application behaviors.
+ Framework-agnostic test helpers — Result-based polling (Polling.WaitAsync) for eventually-consistent checks and HTTP response JSON deserialization. No xUnit dependency; the xUnit-coupled test stack lives in Vulthil.xUnit.
+ testing;polling;eventually-consistent;http;result;helpers
diff --git a/src/Vulthil.Messaging.Abstractions/README.md b/src/Vulthil.Messaging.Abstractions/README.md
index 06161a0a..c49bf45d 100644
--- a/src/Vulthil.Messaging.Abstractions/README.md
+++ b/src/Vulthil.Messaging.Abstractions/README.md
@@ -10,4 +10,4 @@ Messaging contracts for producers/consumers and request/reply boundaries.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj b/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj
index f7ef807c..116a4970 100644
--- a/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj
+++ b/src/Vulthil.Messaging.Abstractions/Vulthil.Messaging.Abstractions.csproj
@@ -2,6 +2,7 @@
Messaging contracts for producers/consumers and request/reply boundaries.
+ messaging;abstractions;contracts;consumer;publisher;request-reply;message-bus
diff --git a/src/Vulthil.Messaging.Inbox.Cosmos/README.md b/src/Vulthil.Messaging.Inbox.Cosmos/README.md
index aa761b0e..edd83023 100644
--- a/src/Vulthil.Messaging.Inbox.Cosmos/README.md
+++ b/src/Vulthil.Messaging.Inbox.Cosmos/README.md
@@ -35,6 +35,6 @@ builder.AddMessaging(messaging =>
```
The marker is a self-contained document keyed and partitioned by `MessageId` (its own container), so duplicate
-inserts conflict and are treated as already-processed. See the
-[Inbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/inbox-pattern.md) article
-for the guarantees and how this differs from the relational store.
+inserts conflict and are treated as already-processed. See the Inbox Pattern article on the
+[documentation site](https://vulthil.github.io/Vulthil.SharedKernel/) for the guarantees and how this differs
+from the relational store.
diff --git a/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj b/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj
index 682374e2..ebe95af2 100644
--- a/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj
+++ b/src/Vulthil.Messaging.Inbox.Cosmos/Vulthil.Messaging.Inbox.Cosmos.csproj
@@ -2,6 +2,7 @@
Azure Cosmos DB idempotency store for Vulthil.Messaging.Inbox — effectively-once message processing (best-effort deduplication layered over idempotent-by-design writes). Cosmos has no cross-partition transaction, so it cannot offer the transactional exactly-once the relational store does.
+ inbox;idempotency;deduplication;cosmosdb;azure-cosmos-db;efcore;messaging
diff --git a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs
index 9f55bd32..f0c1c738 100644
--- a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs
+++ b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/InboxMessage.cs
@@ -12,7 +12,7 @@ public sealed record InboxMessage
public required string MessageId { get; init; }
///
- /// Gets or sets the UTC timestamp at which the message was processed.
+ /// Gets the UTC timestamp at which the message was processed.
///
public DateTimeOffset ProcessedOnUtc { get; init; }
}
diff --git a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj
index f087e5b0..9e7a7c53 100644
--- a/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj
+++ b/src/Vulthil.Messaging.Inbox.EntityFrameworkCore/Vulthil.Messaging.Inbox.EntityFrameworkCore.csproj
@@ -2,6 +2,7 @@
Shared Entity Framework Core primitives for Vulthil.Messaging.Inbox — the InboxMessage marker entity and the ISaveInboxMessages context interface, reused by the relational and Cosmos idempotency stores.
+ inbox;idempotency;deduplication;efcore;entity-framework-core;messaging
diff --git a/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj b/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj
index 250dbac0..4980c6ec 100644
--- a/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj
+++ b/src/Vulthil.Messaging.Inbox.Relational/Vulthil.Messaging.Inbox.Relational.csproj
@@ -2,6 +2,7 @@
Relational Entity Framework Core idempotency store for Vulthil.Messaging.Inbox — transactional exactly-once message processing where the inbox marker and the consumer's writes commit in one transaction. Works with any relational EF Core provider (PostgreSQL, SQL Server, MySQL, SQLite); Cosmos requires a separate store.
+ inbox;idempotency;deduplication;exactly-once;efcore;relational;sql;messaging
diff --git a/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs b/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs
index ad246c30..3e654497 100644
--- a/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs
+++ b/src/Vulthil.Messaging.Inbox/IIdempotencyStore.cs
@@ -23,8 +23,15 @@ public interface IIdempotencyStore
/// skipped.
///
/// The idempotency key for the current delivery.
- /// The message context for the current delivery.
- /// The consumer invocation to run when the delivery is not a duplicate.
+ ///
+ /// The message context for the current delivery — optional input for implementations (e.g. to read metadata
+ /// or headers); the built-in stores do not require it.
+ ///
+ ///
+ /// The consumer invocation to run when the delivery is not a duplicate. The token an implementation passes to
+ /// this callback does not flow into the consumer: the consumer observes the delivery's own
+ /// , so the callback token cannot cancel the consumer body.
+ ///
/// A token to observe for cancellation.
///
/// if ran and the marker was recorded;
diff --git a/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs b/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs
index 5cf464a8..1c311bc1 100644
--- a/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs
+++ b/src/Vulthil.Messaging.Inbox/IdempotentConsumeFilter.cs
@@ -12,9 +12,9 @@ namespace Vulthil.Messaging.Inbox;
///
///
/// The guard only deduplicates. Bounding a consumer that keeps failing — retry count, fault emission, and
-/// dead-lettering — is delegated to the transport (on RabbitMQ: retry up to the queue's max retry count, then a
-/// fault is published and the delivery is nacked to the dead-letter exchange), so this filter deliberately
-/// ignores .
+/// dead-lettering — is delegated to the transport (on RabbitMQ: retry per the consumer's resolved retry policy,
+/// then a fault is published and the delivery is dead-lettered when a dead-letter queue is configured), so this
+/// filter deliberately ignores .
///
/// The consumed message type.
internal sealed class IdempotentConsumeFilter(
diff --git a/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs b/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs
index 39213257..0a1a5087 100644
--- a/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs
+++ b/src/Vulthil.Messaging.Inbox/MissingIdempotencyKeyException.cs
@@ -17,7 +17,7 @@ public MissingIdempotencyKeyException(Type messageType)
}
///
- /// Gets the message type whose delivery lacked an idempotency key, when available.
+ /// Gets the message type whose delivery lacked an idempotency key.
///
public Type MessageType { get; }
}
diff --git a/src/Vulthil.Messaging.Inbox/README.md b/src/Vulthil.Messaging.Inbox/README.md
index 4deda81d..5662277e 100644
--- a/src/Vulthil.Messaging.Inbox/README.md
+++ b/src/Vulthil.Messaging.Inbox/README.md
@@ -42,10 +42,11 @@ deduplication instead, set it on the store registration: `services.AddRelational
## Scope
This package only **deduplicates**. Bounding a persistently-failing consumer is the transport's job: on RabbitMQ
-a poison delivery is retried up to the queue's `MaxRetryCount`, then a `Fault` is published and the message is
-nacked to the dead-letter exchange — the guard ignores `RetryCount` and adds no max-attempts of its own. It also
+a failing delivery is retried per the consumer's retry policy (its registration's, falling back to the queue
+default), then a `Fault` is published and the delivery is dead-lettered when a dead-letter queue is
+configured — the guard ignores `RetryCount` and adds no max-attempts of its own. It also
serializes only the marker *insert*, so two duplicates processed **concurrently** can each run the consumer body
once. Keep side effects idempotent if concurrent duplicate delivery is possible.
-See the [inbox pattern documentation](https://vulthil.github.io/Vulthil.SharedKernel/articles/inbox-pattern.html)
+See the Inbox Pattern article on the [documentation site](https://vulthil.github.io/Vulthil.SharedKernel/)
for the full design and the message-id stability contract.
diff --git a/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj b/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj
index 79145251..26334cde 100644
--- a/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj
+++ b/src/Vulthil.Messaging.Inbox/Vulthil.Messaging.Inbox.csproj
@@ -2,6 +2,7 @@
Idempotent-receiver (inbox) consume filter for Vulthil.Messaging. Provides exactly-once message processing on top of at-least-once delivery via a pluggable, persistence-agnostic idempotency store.
+ inbox;idempotency;idempotent-consumer;deduplication;messaging;exactly-once;consume-filter
diff --git a/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj b/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj
index 6bd0a2fb..81835fe1 100644
--- a/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj
+++ b/src/Vulthil.Messaging.Outbox/Vulthil.Messaging.Outbox.csproj
@@ -2,6 +2,7 @@
Transactional bus-publish outbox for Vulthil.Messaging. A publish/send filter captures messages published inside a database transaction into the shared outbox table; the outbox relay sends them to the broker after the transaction commits, with a stable message id, eliminating the dual-write problem.
+ outbox;transactional-outbox;outbox-pattern;messaging;message-bus;dual-write;reliability
diff --git a/src/Vulthil.Messaging.RabbitMq/README.md b/src/Vulthil.Messaging.RabbitMq/README.md
index c3e40860..8f000b1e 100644
--- a/src/Vulthil.Messaging.RabbitMq/README.md
+++ b/src/Vulthil.Messaging.RabbitMq/README.md
@@ -10,4 +10,4 @@ RabbitMQ implementation for the Vulthil messaging abstractions.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj b/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj
index 1f14aece..21836e95 100644
--- a/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj
+++ b/src/Vulthil.Messaging.RabbitMq/Vulthil.Messaging.RabbitMq.csproj
@@ -2,6 +2,7 @@
RabbitMQ implementation for the Vulthil messaging abstractions.
+ rabbitmq;amqp;messaging;message-bus;pubsub;request-reply;retry;dead-letter
diff --git a/src/Vulthil.Messaging.TestHarness/README.md b/src/Vulthil.Messaging.TestHarness/README.md
index 597ac805..e3ee0df4 100644
--- a/src/Vulthil.Messaging.TestHarness/README.md
+++ b/src/Vulthil.Messaging.TestHarness/README.md
@@ -28,4 +28,4 @@ For an integration test that keeps the production composition, swap the transpor
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj b/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj
index eaf9df2f..fa5b678c 100644
--- a/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj
+++ b/src/Vulthil.Messaging.TestHarness/Vulthil.Messaging.TestHarness.csproj
@@ -2,6 +2,7 @@
Test utilities for validating messaging flows in integration and component tests.
+ testing;test-harness;in-memory;messaging;message-bus;consumers;transport
diff --git a/src/Vulthil.Messaging/ConsumeFilterOptions.cs b/src/Vulthil.Messaging/ConsumeFilterOptions.cs
index 4dce2ac4..00f4fc49 100644
--- a/src/Vulthil.Messaging/ConsumeFilterOptions.cs
+++ b/src/Vulthil.Messaging/ConsumeFilterOptions.cs
@@ -2,20 +2,22 @@ namespace Vulthil.Messaging;
///
/// Configures the built-in consume filters that AddMessaging registers by default.
-/// Each flag toggles a specific filter on or off; setting a flag to
-/// causes the filter to pass deliveries straight through without performing its work.
+/// Each flag toggles a specific filter on or off; a filter whose flag is
+/// is never registered in DI, so deliveries do not pass through it at all.
///
///
-/// Filters remain registered in DI regardless of these flags so that user code can still
-/// resolve them (e.g. for unit tests); the flag is checked at invocation time. To disable
-/// every default filter, set each flag explicitly.
+/// The flags are read once, when AddMessaging composes the pipeline (after the configurator
+/// action has run) — set them via configuration (Messaging:Options:ConsumeFilters) or
+/// ConfigureMessagingOptions inside the configurator. There is no runtime toggle: changing
+/// a flag after registration has no effect.
///
public sealed class ConsumeFilterOptions
{
///
/// When (the default),
/// emits structured Debug logs at the start and end of every consume, plus a Warning log on
- /// uncaught exceptions, with timing information.
+ /// uncaught exceptions, with timing information. When , the filter is
+ /// not registered at all.
///
public bool EnableLogging { get; set; } = true;
}
diff --git a/src/Vulthil.Messaging/IMessageConfigurationProvider.cs b/src/Vulthil.Messaging/IMessageConfigurationProvider.cs
index c435e79b..c2c3f707 100644
--- a/src/Vulthil.Messaging/IMessageConfigurationProvider.cs
+++ b/src/Vulthil.Messaging/IMessageConfigurationProvider.cs
@@ -55,9 +55,9 @@ public interface IMessageConfigurationProvider
IReadOnlyCollection QueueDefinitions { get; }
///
- /// Gets the options controlling which built-in consume filters perform their work at runtime.
- /// Filters check the appropriate flag on this object on every delivery, so toggles take effect
- /// without re-registering the filter.
+ /// Gets the built-in consume-filter flags as they were configured at AddMessaging time.
+ /// The flags are applied once, at registration — a disabled filter is never added to DI — so
+ /// mutating them through this snapshot has no effect on the composed pipeline.
///
ConsumeFilterOptions ConsumeFilters { get; }
diff --git a/src/Vulthil.Messaging/README.md b/src/Vulthil.Messaging/README.md
index 054c8246..e4b7d63b 100644
--- a/src/Vulthil.Messaging/README.md
+++ b/src/Vulthil.Messaging/README.md
@@ -14,4 +14,4 @@ transport uses.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.Messaging/Vulthil.Messaging.csproj b/src/Vulthil.Messaging/Vulthil.Messaging.csproj
index efec166a..03b7eaca 100644
--- a/src/Vulthil.Messaging/Vulthil.Messaging.csproj
+++ b/src/Vulthil.Messaging/Vulthil.Messaging.csproj
@@ -2,6 +2,7 @@
Messaging composition APIs for configuring consumers, queues, and hosted processing.
+ messaging;message-bus;consumers;queues;consume-filters;retry;partitioning;transport-sdk
diff --git a/src/Vulthil.Results/README.md b/src/Vulthil.Results/README.md
index cb5aeaa1..88db65ff 100644
--- a/src/Vulthil.Results/README.md
+++ b/src/Vulthil.Results/README.md
@@ -10,4 +10,4 @@ Small result primitives for explicit success/failure flows without exceptions.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.Results/Vulthil.Results.csproj b/src/Vulthil.Results/Vulthil.Results.csproj
index 1b54731e..6b3aa1b5 100644
--- a/src/Vulthil.Results/Vulthil.Results.csproj
+++ b/src/Vulthil.Results/Vulthil.Results.csproj
@@ -2,6 +2,7 @@
Small result primitives for explicit success/failure flows without exceptions.
+ result;result-pattern;railway-oriented-programming;error-handling;errors;functional
diff --git a/src/Vulthil.SharedKernel.Api/README.md b/src/Vulthil.SharedKernel.Api/README.md
index bde4053a..055a197a 100644
--- a/src/Vulthil.SharedKernel.Api/README.md
+++ b/src/Vulthil.SharedKernel.Api/README.md
@@ -10,4 +10,4 @@ API-layer helpers for endpoints, controllers, and cross-cutting HTTP concerns.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj b/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj
index cbba3e4c..2f9f0431 100644
--- a/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj
+++ b/src/Vulthil.SharedKernel.Api/Vulthil.SharedKernel.Api.csproj
@@ -2,6 +2,7 @@
API-layer helpers for endpoints, controllers, and cross-cutting HTTP concerns.
+ aspnetcore;minimal-api;endpoints;controllers;problemdetails;openapi;result;http
diff --git a/src/Vulthil.SharedKernel.Application/README.md b/src/Vulthil.SharedKernel.Application/README.md
index 2f09b329..b7a95434 100644
--- a/src/Vulthil.SharedKernel.Application/README.md
+++ b/src/Vulthil.SharedKernel.Application/README.md
@@ -10,4 +10,4 @@ Application-layer building blocks such as handlers, pipelines, and validation he
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj b/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj
index ad405f5f..01e19627 100644
--- a/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj
+++ b/src/Vulthil.SharedKernel.Application/Vulthil.SharedKernel.Application.csproj
@@ -2,6 +2,7 @@
Application-layer building blocks such as handlers, pipelines, and validation helpers.
+ cqrs;commands;queries;handlers;pipeline-behaviors;validation;fluentvalidation;application-layer
diff --git a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md
index 448ec1da..c6278fd9 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md
+++ b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/README.md
@@ -10,4 +10,4 @@ Provider-specific EF Core mapping and optimizations for Cosmos DB.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj
index a5689ae5..29556d0a 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj
+++ b/src/Vulthil.SharedKernel.Infrastructure.Cosmos/Vulthil.SharedKernel.Infrastructure.Cosmos.csproj
@@ -2,6 +2,7 @@
Provider-specific EF Core mapping and optimizations for Cosmos DB.
+ cosmosdb;azure-cosmos-db;cosmos;efcore;entity-framework-core;outbox;aspire
diff --git a/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs b/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs
index 7826e36e..b4be3e59 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs
+++ b/src/Vulthil.SharedKernel.Infrastructure.MySql/DependencyInjectionExtensions.cs
@@ -20,12 +20,22 @@ public static class DependencyInjectionExtensions
{
#if NET10_0_OR_GREATER
- /// ...
- /// ...
- /// ...
- /// ...
- /// ...
- /// ... .NET 10 wording ...
+ ///
+ /// Configures the database infrastructure for MySQL: registers a pooled for
+ /// the named connection string and selects the MySQL outbox store (row-level locking via
+ /// FOR UPDATE SKIP LOCKED) unless a custom store was chosen via UseOutboxStore. When outbox
+ /// processing is enabled, the commit-time relay trigger is wired as well.
+ ///
+ ///
+ /// On .NET 10 the Pomelo-compatible EF Core provider is registered directly and the MySQL server version is
+ /// detected from the connection at startup (ServerVersion.AutoDetect), so the database must be reachable
+ /// when the host starts. The underlying registrations are deferred until the configuration chain has executed,
+ /// so the order of UseMySql, EnableOutboxProcessing, and UseOutboxStore does not matter.
+ ///
+ /// The application's DbContext; it must expose the outbox set via ISaveOutboxMessages.
+ /// The database infrastructure configurator.
+ /// The connection-string name resolved from the host's configuration.
+ /// An optional action to configure the EF Core/Pomelo options (e.g. command timeout).
/// The configurator for chaining.
public static IDatabaseInfrastructureConfigurator UseMySql(
this IDatabaseInfrastructureConfigurator configurator,
@@ -34,12 +44,22 @@ public static IDatabaseInfrastructureConfigurator UseMySql UseMySqlCore(configurator, connectionStringKey, configureSettings);
#else
- /// ...
- /// ...
- /// ...
- /// ...
- /// ...
- /// ... .NET 9 wording ...
+ ///
+ /// Configures the database infrastructure for MySQL: registers through the
+ /// Aspire Pomelo integration for the named connection string and selects the MySQL outbox store (row-level
+ /// locking via FOR UPDATE SKIP LOCKED) unless a custom store was chosen via UseOutboxStore. When
+ /// outbox processing is enabled, the commit-time relay trigger is wired as well.
+ ///
+ ///
+ /// On .NET 9 the context is registered via the Aspire.Pomelo.EntityFrameworkCore.MySql client
+ /// integration, which resolves the connection string and adds health checks, telemetry, and connection
+ /// resiliency. The underlying registrations are deferred until the configuration chain has executed, so the
+ /// order of UseMySql, EnableOutboxProcessing, and UseOutboxStore does not matter.
+ ///
+ /// The application's DbContext; it must expose the outbox set via ISaveOutboxMessages.
+ /// The database infrastructure configurator.
+ /// The connection-string name resolved by the Aspire integration.
+ /// An optional action to configure the Aspire integration settings (health checks, tracing, retries).
/// An optional action to configure the DbContext options.
/// The configurator for chaining.
public static IDatabaseInfrastructureConfigurator UseMySql(
diff --git a/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md b/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md
index 67bc539c..7d79f60e 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md
+++ b/src/Vulthil.SharedKernel.Infrastructure.MySql/README.md
@@ -10,4 +10,4 @@ Provider-specific EF Core mapping and optimizations for MySql.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj b/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj
index c8b4ca91..a8986db6 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj
+++ b/src/Vulthil.SharedKernel.Infrastructure.MySql/Vulthil.SharedKernel.Infrastructure.MySql.csproj
@@ -2,6 +2,7 @@
Provider-specific EF Core mapping and optimizations for MySql.
+ mysql;pomelo;efcore;entity-framework-core;outbox;aspire
diff --git a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md
index 1d96ef2e..fdab44af 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md
+++ b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/README.md
@@ -10,4 +10,4 @@ Provider-specific EF Core mapping and optimizations for Npgsql/PostgreSQL.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj
index 49b2332b..3cc71150 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj
+++ b/src/Vulthil.SharedKernel.Infrastructure.Npgsql/Vulthil.SharedKernel.Infrastructure.Npgsql.csproj
@@ -2,6 +2,7 @@
Provider-specific EF Core mapping and optimizations for Npgsql/PostgreSQL.
+ postgresql;postgres;npgsql;efcore;entity-framework-core;outbox;aspire
diff --git a/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md b/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md
index aeedf6b9..9f0828b3 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md
+++ b/src/Vulthil.SharedKernel.Infrastructure.Relational/README.md
@@ -10,4 +10,4 @@ Shared relational helpers for provider implementations.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj b/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj
index fe827b5e..e87ae0a4 100644
--- a/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj
+++ b/src/Vulthil.SharedKernel.Infrastructure.Relational/Vulthil.SharedKernel.Infrastructure.Relational.csproj
@@ -2,6 +2,7 @@
Shared relational helpers for provider implementations.
+ efcore;entity-framework-core;relational;sql;outbox;migrations
diff --git a/src/Vulthil.SharedKernel.Infrastructure/README.md b/src/Vulthil.SharedKernel.Infrastructure/README.md
index 2b130ca0..0ec1fe1e 100644
--- a/src/Vulthil.SharedKernel.Infrastructure/README.md
+++ b/src/Vulthil.SharedKernel.Infrastructure/README.md
@@ -10,4 +10,4 @@ Infrastructure helpers for persistence, transactions, outbox, and EF Core integr
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj b/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj
index dc986c78..7c15c0ee 100644
--- a/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj
+++ b/src/Vulthil.SharedKernel.Infrastructure/Vulthil.SharedKernel.Infrastructure.csproj
@@ -2,6 +2,7 @@
Infrastructure helpers for persistence, transactions, outbox, and EF Core integration.
+ efcore;entity-framework-core;dbcontext;unit-of-work;repository;outbox;infrastructure
diff --git a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md
index e1e17070..652e2f4d 100644
--- a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md
+++ b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/README.md
@@ -2,7 +2,8 @@
[](https://www.nuget.org/packages/Vulthil.SharedKernel.Outbox.EntityFrameworkCore)
-The Entity Framework Core implementation of the [`Vulthil.SharedKernel.Outbox`](../Vulthil.SharedKernel.Outbox)
+The Entity Framework Core implementation of the
+[`Vulthil.SharedKernel.Outbox`](https://www.nuget.org/packages/Vulthil.SharedKernel.Outbox)
engine. It keeps all EF Core coupling out of the engine package:
- `ISaveOutboxMessages` — the `DbSet` marker the application's `DbContext` implements.
@@ -14,5 +15,5 @@ engine. It keeps all EF Core coupling out of the engine package:
- `ApplyOutbox()` — a `ModelBuilder` extension applying the provider-agnostic `OutboxMessage` mapping. Provider packages offer optimized alternatives (`ApplyNpgsqlOutbox()`, `ApplyMySqlOutbox()`, `ApplyCosmosOutbox()`).
Most applications consume this transitively via `Vulthil.SharedKernel.Infrastructure` (`EnableOutboxProcessing`) and
-a provider package (`UseNpgsql`, `UseMySql`, `UseCosmosDb`). See the
-[Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md) article.
+a provider package (`UseNpgsql`, `UseMySql`, `UseCosmosDb`). See the Outbox Pattern article on the
+[documentation site](https://vulthil.github.io/Vulthil.SharedKernel/).
diff --git a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj
index a82e96dc..a7c8fba2 100644
--- a/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj
+++ b/src/Vulthil.SharedKernel.Outbox.EntityFrameworkCore/Vulthil.SharedKernel.Outbox.EntityFrameworkCore.csproj
@@ -2,6 +2,7 @@
Entity Framework Core implementation of the Vulthil.SharedKernel.Outbox engine — the IOutboxStore (capture + the transactional relay batch unit), the ISaveOutboxMessages context marker, the domain-event capture interceptor, and the OutboxMessage mapping. Referenced by the relational/provider store packages and the infrastructure host; keeps the outbox engine itself free of any EF Core dependency.
+ outbox;transactional-outbox;outbox-pattern;efcore;entity-framework-core;domain-events
diff --git a/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs b/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs
index 5fd95225..ee50db63 100644
--- a/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs
+++ b/src/Vulthil.SharedKernel.Outbox/OutboxProcessingOptions.cs
@@ -23,7 +23,8 @@ public sealed class OutboxProcessingOptions
[Range(1, int.MaxValue)]
public int BatchSize { get; set; } = 10;
///
- /// Gets the maximum number of delivery attempts before a message is marked as processed. Default is 3.
+ /// Gets the maximum number of delivery attempts before a message is dead-lettered — its FailedOnUtc
+ /// is set and it is no longer relayed. Default is 3.
///
[Range(1, int.MaxValue)]
public int MaxRetries { get; set; } = 3;
diff --git a/src/Vulthil.SharedKernel.Outbox/README.md b/src/Vulthil.SharedKernel.Outbox/README.md
index c896eb8f..39dcb4a1 100644
--- a/src/Vulthil.SharedKernel.Outbox/README.md
+++ b/src/Vulthil.SharedKernel.Outbox/README.md
@@ -3,13 +3,14 @@
[](https://www.nuget.org/packages/Vulthil.SharedKernel.Outbox)
The transactional **outbox engine** for `Vulthil.SharedKernel`: the message-capture model (`OutboxMessage`), the
-relay processor and background service, pluggable sinks (`IOutboxDispatcher`), the commit-time relay signal, and the
-provider-agnostic strategy contracts (`IOutboxStrategy` / `BaseOutboxStrategy`).
+relay processor and background service, pluggable dispatchers (`IOutboxDispatcher`), the commit-time relay signal,
+and the persistence-agnostic `IOutboxStore` seam.
-It is intentionally persistence-light — it carries the outbox engine without the rest of
-`Vulthil.SharedKernel.Infrastructure` — so a messaging bridge (such as `Vulthil.Messaging.Outbox`) can depend on the
-engine alone. The full infrastructure package (`Vulthil.SharedKernel.Infrastructure`) references this engine and adds
-the `DbContext` base, the database-infrastructure configurator, and the DI wiring (`EnableOutboxProcessing`).
+It is intentionally persistence-light — free of any EF Core dependency — so a messaging bridge (such as
+`Vulthil.Messaging.Outbox`) can depend on the engine alone. The EF Core implementation lives in
+`Vulthil.SharedKernel.Outbox.EntityFrameworkCore`, and the full infrastructure package
+(`Vulthil.SharedKernel.Infrastructure`) references the engine and adds the `DbContext` base, the
+database-infrastructure configurator, and the DI wiring (`EnableOutboxProcessing`).
-See the [Outbox Pattern](https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/outbox-pattern.md)
-article for the design and usage.
+See the Outbox Pattern article on the [documentation site](https://vulthil.github.io/Vulthil.SharedKernel/)
+for the design and usage.
diff --git a/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj b/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj
index 2a49107a..b6a56d44 100644
--- a/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj
+++ b/src/Vulthil.SharedKernel.Outbox/Vulthil.SharedKernel.Outbox.csproj
@@ -2,6 +2,7 @@
Transactional outbox engine for Vulthil.SharedKernel — the message-capture model, the relay processor and background service, pluggable dispatchers, the commit-time relay signal, and the persistence-agnostic IOutboxStore seam. Free of any EF Core dependency; an EF implementation lives in Vulthil.SharedKernel.Outbox.EntityFrameworkCore.
+ outbox;transactional-outbox;outbox-pattern;domain-events;messaging;reliability;background-service
diff --git a/src/Vulthil.SharedKernel/README.md b/src/Vulthil.SharedKernel/README.md
index 981bc0fd..2720b411 100644
--- a/src/Vulthil.SharedKernel/README.md
+++ b/src/Vulthil.SharedKernel/README.md
@@ -10,4 +10,4 @@ Core domain primitives and base abstractions for shared domain logic.
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj b/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj
index 80642e37..e5863bae 100644
--- a/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj
+++ b/src/Vulthil.SharedKernel/Vulthil.SharedKernel.csproj
@@ -2,6 +2,7 @@
Core domain primitives and base abstractions for shared domain logic.
+ ddd;domain-driven-design;shared-kernel;entity;aggregate-root;domain-events;building-blocks
diff --git a/src/Vulthil.xUnit.Cosmos/README.md b/src/Vulthil.xUnit.Cosmos/README.md
index 577d4249..064b4b96 100644
--- a/src/Vulthil.xUnit.Cosmos/README.md
+++ b/src/Vulthil.xUnit.Cosmos/README.md
@@ -20,4 +20,4 @@ internal sealed class CosmosTestContainer(IMessageSink messageSink)
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj b/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj
index 8b3db2a5..1c6f32d9 100644
--- a/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj
+++ b/src/Vulthil.xUnit.Cosmos/Vulthil.xUnit.Cosmos.csproj
@@ -2,6 +2,7 @@
Azure Cosmos DB emulator fixture for Vulthil.xUnit — wraps the Cosmos emulator Testcontainer, registers the DbContext against it, and gives every consuming factory its own emulator database for parallel-safe integration tests.
+ xunit;testing;integration-testing;cosmosdb;azure-cosmos-db;cosmos-emulator;testcontainers
diff --git a/src/Vulthil.xUnit/Http/IHttpMock.cs b/src/Vulthil.xUnit/Http/IHttpMock.cs
index 05f88310..f131f03f 100644
--- a/src/Vulthil.xUnit/Http/IHttpMock.cs
+++ b/src/Vulthil.xUnit/Http/IHttpMock.cs
@@ -5,7 +5,8 @@ namespace Vulthil.xUnit.Http;
///
/// An in-process fake for an outbound dependency. Configure stubbed responses per test
/// and inspect the requests the system under test sent. Register one per typed client via
-/// AddHttpMock<TClient, TImplementation>() on the factory and retrieve it with HttpMock<TClient>().
+/// AddHttpMock<TClient>() (or per named client via AddHttpMock("name")) on the factory and
+/// retrieve it with HttpMock<TClient>() (or HttpMock("name")).
///
public interface IHttpMock : IResettableResource
{
diff --git a/src/Vulthil.xUnit/README.md b/src/Vulthil.xUnit/README.md
index c799887c..9769ddab 100644
--- a/src/Vulthil.xUnit/README.md
+++ b/src/Vulthil.xUnit/README.md
@@ -4,10 +4,13 @@
Reusable xUnit base infrastructure for integration and unit test composition: auto-mocked unit test base classes, a `WebApplicationFactory` with Testcontainers and HTTP-mock support, and an assembly-wide `ContainerHost` that shares containers across parallel test classes through isolated per-class scopes built into the fixture base classes (a database per class for database containers, a virtual host per class for RabbitMQ; Cosmos lives in `Vulthil.xUnit.Cosmos`).
+This package is xUnit-coupled by design; the framework-agnostic helpers (`Result`-based polling, HTTP response
+deserialization) live in `Vulthil.Extensions.Testing`.
+
## Install
`dotnet add package Vulthil.xUnit`
## Docs
-Usage patterns: https://github.com/Vulthil/Vulthil.SharedKernel/tree/main/docs/articles/packages
+Usage patterns and articles: https://vulthil.github.io/Vulthil.SharedKernel/
diff --git a/src/Vulthil.xUnit/Vulthil.xUnit.csproj b/src/Vulthil.xUnit/Vulthil.xUnit.csproj
index 41518563..ce7eccaa 100644
--- a/src/Vulthil.xUnit/Vulthil.xUnit.csproj
+++ b/src/Vulthil.xUnit/Vulthil.xUnit.csproj
@@ -2,6 +2,7 @@
Reusable xUnit base infrastructure for integration and unit test composition.
+ xunit;testing;integration-testing;testcontainers;webapplicationfactory;automocker;respawn;http-mock