diff --git a/CLAUDE.md b/CLAUDE.md index 9795716..a5bf6f2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -309,18 +309,24 @@ services.AddBrighter() ✅ **V10 — current** ```csharp -services.AddBrighter() - .AddProducers(options => +services.AddConsumers(options => { - options.ProducerRegistry = new RmqProducerRegistryFactory(...).Create(); + options.Subscriptions = subscriptions; + options.DefaultChannelFactory = new ChannelFactory(...); }) - .AddConsumers(options => + .AddProducers(options => { - options.Subscriptions = subscriptions; - options.ChannelFactory = new ChannelFactory(...); + options.ProducerRegistry = new RmqProducerRegistryFactory(...).Create(); }); ``` +**The receiver matters and the compiler is the only thing that says so.** `AddConsumers` +extends `IServiceCollection`; `AddProducers`, `UseScheduler`, `Handlers` and +`AutoFromAssemblies` extend the `IBrighterBuilder` it returns. So a consumer registration comes +**first** and everything else chains off it — +`services.AddBrighter().AddProducers(…).AddConsumers(…)` is **`CS1929`**, and an earlier +version of this example had it. `AddBrighter` is the entry point when there are no consumers. + ### Complete code blocks - Every fence carries a language tag: ```` ```csharp ````, ```` ```yaml ````, diff --git a/contents/AgreementDispatcher.md b/contents/AgreementDispatcher.md index 4a49b94..6b30a62 100644 --- a/contents/AgreementDispatcher.md +++ b/contents/AgreementDispatcher.md @@ -114,6 +114,11 @@ registry.RegisterAsync((request, context) => Agreement Dispatcher can be combined with [Dynamic Message Deserialization](DynamicMessageDeserialization.md) for two-level routing: ```csharp +using Paramore.Brighter; +using Paramore.Brighter.Extensions.DependencyInjection; +using Paramore.Brighter.MessagingGateway.Kafka; +using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection; + // Level 1: Dynamic deserialization (CloudEvents type → Request type) var subscription = new KafkaSubscription( new SubscriptionName("paramore.example.orders"), @@ -130,8 +135,7 @@ var subscription = new KafkaSubscription( ); // Level 2: Agreement dispatcher (Request content → Handler) -services.AddBrighter(options => { }) - .AddConsumers(options => { options.Subscriptions = new[] { subscription }; }) +services.AddConsumers(options => { options.Subscriptions = new[] { subscription }; }) .Handlers(registry => { registry.Register((request, context) => diff --git a/contents/AzureServiceBusConfiguration.md b/contents/AzureServiceBusConfiguration.md index f4ef919..a54d6d4 100644 --- a/contents/AzureServiceBusConfiguration.md +++ b/contents/AzureServiceBusConfiguration.md @@ -152,6 +152,7 @@ is `Proactor`. This is a typical *Subscription* configuration in a Consumer application: ``` csharp +// ... private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCollection services) { var subscriptions = new Subscription[] @@ -192,7 +193,7 @@ private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCo builder.Services.AddConsumers(options => { options.Subscriptions = subscriptions; - options.ChannelFactory = new AzureServiceBusChannelFactory(asbConsumerFactory); + options.DefaultChannelFactory = new AzureServiceBusChannelFactory(asbConsumerFactory); } ``` diff --git a/contents/DynamoInbox.md b/contents/DynamoInbox.md index 352dc46..8ca188f 100644 --- a/contents/DynamoInbox.md +++ b/contents/DynamoInbox.md @@ -43,7 +43,7 @@ private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCo services.AddConsumers(opt => { - opt.Inbox = new InboxConfiguration(new DynamoDbInbox(dynamoDb, new DynamoDbInboxConfiguration())); + opt.InboxConfiguration = new InboxConfiguration(new DynamoDbInbox(dynamoDb, new DynamoDbInboxConfiguration())); ... }); } diff --git a/contents/InMemoryInbox.md b/contents/InMemoryInbox.md index 43ffe5d..0a2fd19 100644 --- a/contents/InMemoryInbox.md +++ b/contents/InMemoryInbox.md @@ -33,18 +33,15 @@ The InMemory Inbox provides message deduplication without requiring a database. // ... var bus = new InternalBus(); -services.AddBrighter(options => +services.AddConsumers(options => { options.HandlerLifetime = ServiceLifetime.Scoped; -}) -.AddConsumers(options => -{ - options.Inbox = new InboxConfiguration( + options.InboxConfiguration = new InboxConfiguration( new InMemoryInbox(TimeProvider.System), - InboxConfiguration.NoActionOnExists + actionOnExists: OnceOnlyAction.Warn ); options.Subscriptions = subscriptions; - options.ChannelFactory = new InMemoryChannelFactory(bus); + options.DefaultChannelFactory = new InMemoryChannelFactory(bus, TimeProvider.System); }) .AutoFromAssemblies(); ``` diff --git a/contents/InMemoryOptions.md b/contents/InMemoryOptions.md index 601a746..7ea6d1e 100644 --- a/contents/InMemoryOptions.md +++ b/contents/InMemoryOptions.md @@ -83,6 +83,13 @@ For more details on service provider overloads and the Options pattern, see [Ser Here's a complete example showing how to use multiple InMemory components together: ```csharp +using Microsoft.Extensions.DependencyInjection; +using Paramore.Brighter; +using Paramore.Brighter.Extensions.DependencyInjection; +using Paramore.Brighter.Inbox; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection; + public class IntegrationTests : IDisposable { private readonly ServiceProvider _serviceProvider; @@ -95,24 +102,14 @@ public class IntegrationTests : IDisposable var services = new ServiceCollection(); var internalBus = new InternalBus(); - services.AddBrighter(options => + services.AddConsumers(options => { options.HandlerLifetime = ServiceLifetime.Scoped; - }) - .AddProducers(options => - { - var publication = new Publication() { Topic = new RoutingKey("PersonCreated") }; - options.ProducerRegistry = new InMemoryProducerRegistryFactory(_internalBus , new[] { publication }, InstrumentationOptions.All) - .Create(); - options.Outbox = new InMemoryOutbox(); - }) - .AddConsumers(options => - { // InMemory Inbox for deduplication - options.Inbox = new InboxConfiguration( + options.InboxConfiguration = new InboxConfiguration( new InMemoryInbox(TimeProvider.System), - InboxConfiguration.NoActionOnExists + actionOnExists: OnceOnlyAction.Warn ); options.Subscriptions = new Subscription[] @@ -124,7 +121,15 @@ public class IntegrationTests : IDisposable ) }; - options.ChannelFactory = new InMemoryChannelFactory(_internalBus, TimeProvider.System); + options.DefaultChannelFactory = new InMemoryChannelFactory(_internalBus, TimeProvider.System); + }) + .AddProducers(options => + { + var publication = new Publication() { Topic = new RoutingKey("PersonCreated") }; + + options.ProducerRegistry = new InMemoryProducerRegistryFactory(_internalBus, new[] { publication }, InstrumentationOptions.All) + .Create(); + options.Outbox = new InMemoryOutbox(TimeProvider.System); }) .UseScheduler(new InMemorySchedulerFactory()) // InMemory Scheduler .UseInMemoryArchiveProvider() // InMemory Archive @@ -182,6 +187,15 @@ public class IntegrationTests : IDisposable Use InMemory components for development/testing, production components elsewhere: ```csharp +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Paramore.Brighter; +using Paramore.Brighter.Extensions.DependencyInjection; +using Paramore.Brighter.Inbox; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection; + public static class BrighterConfiguration { public static IServiceCollection AddBrighterWithEnvironmentConfig( @@ -191,9 +205,12 @@ public static class BrighterConfiguration { var internalBus = new InternalBus(); - services.AddBrighter(options => + services.AddConsumers(options => { options.HandlerLifetime = ServiceLifetime.Scoped; + options.InboxConfiguration = GetInbox(environment, configuration); + options.Subscriptions = GetSubscriptions(); + options.DefaultChannelFactory = GetChannelFactory(environment, configuration, internalBus); }) .AddProducers(options => { @@ -201,12 +218,6 @@ public static class BrighterConfiguration }) .UseOutbox(GetOutbox(environment, configuration)) .UseScheduler(GetSchedulerFactory(environment, configuration)) - .AddConsumers(options => - { - options.Inbox = GetInbox(environment, configuration); - options.Subscriptions = GetSubscriptions(); - options.ChannelFactory = GetChannelFactory(environment, configuration, internalBus); - }) .AutoFromAssemblies(); return services; @@ -215,7 +226,7 @@ public static class BrighterConfiguration private static IAmAProducerRegistry GetProducerRegistry( IHostEnvironment environment, IConfiguration configuration, - IAmABus bus) + InternalBus bus) { if (environment.IsDevelopment() || environment.IsEnvironment("Testing")) { @@ -263,25 +274,25 @@ public static class BrighterConfiguration { return new InboxConfiguration( new InMemoryInbox(TimeProvider.System), - InboxConfiguration.NoActionOnExists + actionOnExists: OnceOnlyAction.Warn ); } // Production: SQL Server, PostgreSQL, MySQL, DynamoDB, etc. return new InboxConfiguration( new MsSqlInbox(/* production config */), - InboxConfiguration.NoActionOnExists + actionOnExists: OnceOnlyAction.Warn ); } private static IAmAChannelFactory GetChannelFactory( IHostEnvironment environment, IConfiguration configuration, - IAmABus bus) + InternalBus bus) { if (environment.IsDevelopment() || environment.IsEnvironment("Testing")) { - return new InMemoryChannelFactory(bus); + return new InMemoryChannelFactory(bus, TimeProvider.System); } // Production: RabbitMQ, Kafka, AWS SQS, etc. diff --git a/contents/InMemoryTransport.md b/contents/InMemoryTransport.md index b7a64b5..61d2422 100644 --- a/contents/InMemoryTransport.md +++ b/contents/InMemoryTransport.md @@ -71,17 +71,15 @@ services.AddBrighter(options => var internalBus = new InternalBus(); -services.AddBrighter(options => +services.AddConsumers(options => { options.HandlerLifetime = ServiceLifetime.Scoped; -}) -.AddConsumers(options => -{ options.Subscriptions = subscriptions; - options.ChannelFactory = new InMemoryChannelFactory(internalBus, TimeProvider.System); + options.DefaultChannelFactory = new InMemoryChannelFactory(internalBus, TimeProvider.System); }) -.AutoFromAssemblies() -.AddHostedService(); +.AutoFromAssemblies(); + +services.AddHostedService(); ``` ## InMemory Subscription Options @@ -135,19 +133,9 @@ public class Startup { var internalBus = new InternalBus(); - services.AddBrighter(options => + services.AddConsumers(options => { options.HandlerLifetime = ServiceLifetime.Scoped; - }) - .AddProducers(options => - { - var publication = new Publication() { Topic = new RoutingKey("GreetingMade") }; - - options.ProducerRegistry = new InMemoryProducerRegistryFactory(internalBus , new[] { publication }, InstrumentationOptions.All) - .Create(); - }) - .AddConsumers(options => - { options.Subscriptions = new Subscription[] { new Subscription( @@ -156,10 +144,18 @@ public class Startup new RoutingKey("GreetingMade") ) }; - options.ChannelFactory = new InMemoryChannelFactory(internalBus, TimeProvider.System); + options.DefaultChannelFactory = new InMemoryChannelFactory(internalBus, TimeProvider.System); + }) + .AddProducers(options => + { + var publication = new Publication() { Topic = new RoutingKey("GreetingMade") }; + + options.ProducerRegistry = new InMemoryProducerRegistryFactory(internalBus, new[] { publication }, InstrumentationOptions.All) + .Create(); }) - .AutoFromAssemblies() - .AddHostedService(); + .AutoFromAssemblies(); + + services.AddHostedService(); } } ``` diff --git a/contents/KafkaConfiguration.md b/contents/KafkaConfiguration.md index 071a027..1c57b64 100644 --- a/contents/KafkaConfiguration.md +++ b/contents/KafkaConfiguration.md @@ -370,6 +370,7 @@ Reactor rather than the Proactor. The following example shows how a subscription might be configured: ``` csharp +// ... private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCollection services) { var subscriptions = new KafkaSubscription[] @@ -394,7 +395,7 @@ The following example shows how a subscription might be configured: services.AddConsumers(options => { options.Subscriptions = subscriptions; - options.ChannelFactory = new ChannelFactory(consumerFactory); + options.DefaultChannelFactory = new ChannelFactory(consumerFactory); }).AutoFromAssemblies(); @@ -411,6 +412,7 @@ The **configHook** takes a *delegate* (you can pass a lambda). Your delegate wil You can use it as follows: ``` csharp +// ... var subscription = new KafkaSubscription( subscriptionName: new SubscriptionName("paramore.example.greeting"), channelName: new ChannelName("greeting.event"), @@ -439,7 +441,7 @@ var consumerFactory = new KafkaMessageConsumerFactory( services.AddConsumers(options => { options.Subscriptions = subscriptions; - options.ChannelFactory = new ChannelFactory(consumerFactory); + options.DefaultChannelFactory = new ChannelFactory(consumerFactory); }).AutoFromAssemblies(); ``` diff --git a/contents/MSSQLInbox.md b/contents/MSSQLInbox.md index 93f9a6a..a3d6bf8 100644 --- a/contents/MSSQLInbox.md +++ b/contents/MSSQLInbox.md @@ -17,6 +17,7 @@ For this we will need the *Inbox* packages for the MsSQL *Inbox*. * **Paramore.Brighter.Inbox.MsSql** ``` csharp +// ... private static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureServices(hostContext, services) => @@ -29,7 +30,7 @@ private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCo services.AddConsumers(options => { var configuration = new RelationalDatabaseConfiguration(connectionString, "BrighterTests", inboxTableName: "InboxMessages"); - opt.Inbox = new InboxConfiguration(new MsSqlInbox(configuration)); + options.InboxConfiguration = new InboxConfiguration(new MsSqlInbox(configuration)); ... }); } diff --git a/contents/PostgreSQLMessageBroker.md b/contents/PostgreSQLMessageBroker.md index 72ac4ca..3cc6148 100644 --- a/contents/PostgreSQLMessageBroker.md +++ b/contents/PostgreSQLMessageBroker.md @@ -180,7 +180,7 @@ var channelFactory = new PostgresChannelFactory(postgresConfiguration); services.AddConsumers(options => { options.Subscriptions = subscriptions; - options.ChannelFactory = channelFactory; + options.DefaultChannelFactory = channelFactory; }) .AutoFromAssemblies(); ``` diff --git a/contents/RabbitMQConfiguration.md b/contents/RabbitMQConfiguration.md index caf2abb..4862b83 100644 --- a/contents/RabbitMQConfiguration.md +++ b/contents/RabbitMQConfiguration.md @@ -277,6 +277,7 @@ and supplies four defaults the table cannot: `requestType` is `T`, `subscription This is a typical *Subscription* configuration in a Consumer application: ``` csharp +// ... private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCollection services) { var subscriptions = new Subscription[] @@ -307,7 +308,7 @@ private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCo services.AddConsumers(options => { options.Subscriptions = subscriptions; - options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory); + options.DefaultChannelFactory = new ChannelFactory(rmqMessageConsumerFactory); ... //see Basic Configuration }) ``` diff --git a/contents/RoutingMultipleMessageTypes.md b/contents/RoutingMultipleMessageTypes.md index 58ff7a2..17373e6 100644 --- a/contents/RoutingMultipleMessageTypes.md +++ b/contents/RoutingMultipleMessageTypes.md @@ -167,8 +167,7 @@ Once the request type is resolved, Brighter routes the message to the appropriat ```csharp // ... -services.AddBrighter(options => { }) - .AddConsumers(options => +services.AddConsumers(options => { options.Subscriptions = new[] { subscription }; }) @@ -208,8 +207,7 @@ var subscription = new KafkaSubscription( ); // Second: Dynamically choose handler based on content -services.AddBrighter(options => { }) - .AddConsumers(options => { options.Subscriptions = new[] { subscription }; }) +services.AddConsumers(options => { options.Subscriptions = new[] { subscription }; }) .Handlers(registry => { registry.Register((request, context) => @@ -266,8 +264,7 @@ var subscription = new KafkaSubscription( messagePumpType: MessagePumpType.Proactor ); -services.AddBrighter(options => { }) - .AddConsumers(options => +services.AddConsumers(options => { options.Subscriptions = new[] { subscription }; }) diff --git a/contents/V10MigrationGuide.md b/contents/V10MigrationGuide.md index 88b926d..2126cb3 100644 --- a/contents/V10MigrationGuide.md +++ b/contents/V10MigrationGuide.md @@ -163,28 +163,36 @@ services.AddBrighter() **After (V10)**: ```csharp -services.AddBrighter() - .AddProducers(options => +// ... +services.AddConsumers(options => { - options.ProducerRegistry = new RmqProducerRegistryFactory(...).Create(); + options.Subscriptions = subscriptions; + options.DefaultChannelFactory = new ChannelFactory(...); }) - .AddConsumers(options => + .AddProducers(options => { - options.Subscriptions = subscriptions; - options.ChannelFactory = new ChannelFactory(...); + options.ProducerRegistry = new RmqProducerRegistryFactory(...).Create(); }); ``` +`AddConsumers` extends `IServiceCollection` and `AddProducers` extends the Brighter builder it +returns, so the consumer registration comes first and the producer registration chains off it. +`AddBrighter` is the entry point when there are no consumers. + **Migration Steps**: 1. Replace `UseExternalBus` with `AddProducers` -2. Replace `AddServiceActivator` with `AddConsumers` -3. Update property names: `ProducerRegistry` instead of passing directly +2. Replace `AddServiceActivator` with `AddConsumers`, and put it **first** in the chain +3. Update property names: `ProducerRegistry` instead of passing directly, + `DefaultChannelFactory` instead of `ChannelFactory`, and `InboxConfiguration` instead of + `Inbox` | V9 Method | V10 Method | Purpose | |-----------|------------|---------| | `UseExternalBus()` | `AddProducers()` | Configure message producers (send/publish to external bus) | | `AddServiceActivator()` | `AddConsumers()` | Configure message consumers (receive from external bus) | +| `options.ChannelFactory` | `options.DefaultChannelFactory` | Supply the channel factory for every subscription that does not name its own | +| `options.Inbox` | `options.InboxConfiguration` | Configure the Inbox used by the consumer pipeline | **Terminology**: In V10, we use **"Dispatcher"** to refer to the component that dispatches messages to handlers. The assembly name remains `Paramore.Brighter.ServiceActivator` for backward compatibility, but documentation and APIs now use "Dispatcher" for clarity. @@ -601,12 +609,25 @@ V10 provides comprehensive InMemory implementations for testing. **Example**: ```csharp +using Microsoft.Extensions.DependencyInjection; +using Paramore.Brighter; +using Paramore.Brighter.Extensions.DependencyInjection; +using Paramore.Brighter.Inbox; +using Paramore.Brighter.Observability; +using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection; + // Test setup with InMemory components var internalBus = new InternalBus(); -services.AddBrighter(options => +services.AddConsumers(options => { options.HandlerLifetime = ServiceLifetime.Scoped; + options.InboxConfiguration = new InboxConfiguration( + new InMemoryInbox(TimeProvider.System), + actionOnExists: OnceOnlyAction.Warn + ); + options.Subscriptions = subscriptions; + options.DefaultChannelFactory = new InMemoryChannelFactory(internalBus, TimeProvider.System); }) .AddProducers(options => { @@ -618,15 +639,6 @@ services.AddBrighter(options => ).Create(); options.Outbox = new InMemoryOutbox(TimeProvider.System); }) -.AddConsumers(options => -{ - options.Inbox = new InboxConfiguration( - new InMemoryInbox(TimeProvider.System), - InboxConfiguration.NoActionOnExists - ); - options.Subscriptions = subscriptions; - options.ChannelFactory = new InMemoryChannelFactory(internalBus, TimeProvider.System); -}) .UseScheduler(new InMemorySchedulerFactory()) .AutoFromAssemblies(); ``` diff --git a/spec/012-configuration_reference/tasks.md b/spec/012-configuration_reference/tasks.md index 11e58f2..c6e41c3 100644 --- a/spec/012-configuration_reference/tasks.md +++ b/spec/012-configuration_reference/tasks.md @@ -1502,19 +1502,55 @@ the only two definitions in the product), so chaining it off the Brighter builde **`CS1929`** — measured in the harness, with a control: deleting that one line from the same file builds clean. -**Eleven blocks across eight pages show the chain** — `InMemoryTransport.md` (2), -`InMemoryOptions.md` (2), `RoutingMultipleMessageTypes.md` (3), `AgreementDispatcher.md`, -`DistributedLock.md`, `InMemoryInbox.md` and `V10MigrationGuide.md` — **and so does -`CLAUDE.md`'s own ✅ *V10 — current* example**, which is where a writer would go to copy it. -The tutorials are **not** affected: they write `builder.Services.AddConsumers(…)`, which is why -009's compile-and-run pass never met this. - -**Nothing was fixed here.** It is not an option-table mismatch, so it is not a ledger entry; it -is eight pages plus `CLAUDE.md`, which is its own coherent unit and its own PR. Recorded before -fixing, per standing obligation 10's spirit if not its letter. **The general shape is the one -009 left behind and this phase paid for again: compiling a page's own fences finds the class of -defect that reading them cannot**, and the block that found it was on a page that does not -carry the defect. +**Eleven blocks across six pages show the chain** — `RoutingMultipleMessageTypes.md` (3), +`InMemoryTransport.md` (2), `InMemoryOptions.md` (2), `V10MigrationGuide.md` (2), +`AgreementDispatcher.md` and `InMemoryInbox.md` — **and so does `CLAUDE.md`'s own ✅ *V10 — +current* example**, which is where a writer would go to copy it. The tutorials are **not** +affected: they write `builder.Services.AddConsumers(…)`, which is why 009's compile-and-run +pass never met this. + +**`DistributedLock.md` matched the pattern and is correct**, which is the reason the count above +reads six pages and not eight: its chain is rooted at `services.AddSingleton(…)`, +which returns `IServiceCollection`, so `.AddConsumers(…).AddProducers(…).UseOutboxSweeper(…)` is +a legal sequence. **A leading-dot `.AddConsumers(` is not the defect; the receiver is** — and the +first draft of this note said eight pages because it counted the pattern. + +**Nothing was fixed in #135.** It is not an option-table mismatch, so it is not a ledger entry; +it is its own coherent unit and its own PR. Recorded before fixing, per standing obligation 10's +spirit if not its letter. **The general shape is the one 009 left behind and this phase paid for +again: compiling a page's own fences finds the class of defect that reading them cannot**, and +the block that found it was on a page that does not carry the defect. + +#### What the repair found once it started — five invented APIs, not one + +**Authorised by the maintainer on 2026-08-30 and shipped as its own PR**, ahead of phase 7. The +chain was the entry point; the blocks it lives in carried four more, every one of which would +stop a reader's build: + +| # | The corpus said | The assembly says | Sites | +|---:|---|---|---:| +| 1 | `AddBrighter().AddProducers(…).AddConsumers(…)` | `AddConsumers` extends `IServiceCollection`; put it first — `CS1929` otherwise | 11 blocks, 6 pages + `CLAUDE.md` | +| 2 | `ConsumersOptions.ChannelFactory` | `DefaultChannelFactory` | 13 | +| 3 | `ConsumersOptions.Inbox` | `InboxConfiguration` | 6 | +| 4 | `InboxConfiguration.NoActionOnExists` | no such member; it is `actionOnExists: OnceOnlyAction.Warn`, and the second positional parameter is an `InboxScope` | 5 | +| 5 | `new InMemoryChannelFactory(bus)` | the only constructor takes `(InternalBus, TimeProvider, …)` | 2 | +| 6 | `.AutoFromAssemblies().AddHostedService<…>()` | `AddHostedService` extends `IServiceCollection`, not the builder | 2 | + +*(A seventh, local to one page: `InMemoryOptions.md`'s two helpers declared their bus parameter +`IAmABus` and passed it to `InMemoryProducerRegistryFactory` and `InMemoryChannelFactory`, both +of which take `InternalBus`.)* + +**Each corrected shape was compiled**, not reviewed: four representative shapes — consumer-only, +consumers-then-producers, the inbox configuration, and consumers-then-`Handlers` — in one project +against the same pinned packages, **0 errors and 0 warnings**. The harness itself supplied the +last lesson of the exercise, and it is one this file already carries: pinning +`Microsoft.Extensions.Hosting 9.0.0` beside `…ServiceActivator.Extensions.Hosting 10.7.0` is +**`NU1605`**, the same downgrade rung 2 met. + +**The using-directive debt moved 787 → 783.** Nine blocks were pulled into `--changed` scope by +edits of one word; four of them earned real `using` directives and five declared `// ...`, +which downgrades and still counts. One of the five was **truncated** — `AzureServiceBusConfiguration.md`'s +consumer example ended mid-lambda with an unbalanced brace — and is now closed. #### The ledger — no entries, and that is what a new page means