Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ jobs:
python-version: '3.12'
- run: python3 tools/linkcheck.py

# Repo-wide: banner, heading uniqueness and terminology are errors on every
# page. The using-directive debt and the untagged fences stay warnings here,
# so the existing backlog is visible without blocking unrelated work.
# Repo-wide: banner, heading uniqueness, terminology and language tags are
# errors on every page. Language tags joined that list with spec 011 Task
# 7.2, which tagged the last 34 untagged fences — a rule with no remaining
# debt needs no softer level. The using-directive debt does still have one,
# so it stays a counted warning here and the backlog is visible without
# blocking unrelated work.
- run: python3 tools/pagelint.py

# On a pull request the code rules become errors, but only for blocks that
Expand Down
24 changes: 22 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ Sections mirror `SUMMARY.md`. Each line is `- [Title](path): Type — one senten
python3 tools/pagelint.py # whole repo
python3 tools/pagelint.py contents/Glossary.md # specific pages
python3 tools/pagelint.py --changed origin/master # strict on changed blocks
python3 tools/pagelint.py --fix # repair, then report what is left
```

Exit code is 1 when anything is an error, 0 when clean or warnings only, 2 on bad
Expand All @@ -319,15 +320,18 @@ nothing beyond the typo, which is the point: a rule that makes small corrections
expensive stops people making them.

**The ledger.** Every convention above maps to a rule, and every rule maps back. A
rule in only one of the two places is how the next round of decay begins:
rule in only one of the two places is how the next round of decay begins — and the
`NO H1` row is there because the acceptance pass found it missing, which is the exact
failure the claim in this paragraph is meant to prevent:

| Convention | Rule | Repo-wide | `--changed` |
|---|---|---|---|
| One H1 per file, before the banner | 1's precondition (`NO H1`) | error | error |
| Banner present as the first non-blank line after the H1 | 1 | error | error |
| Banner matches `BANNER_RE` — type in vocabulary, *Applies to* present | 2 | error | error |
| Heading qualification, across pages (`##`, allowlist exempt) | 3a | error | error |
| Heading qualification, within a page (`##`–`####`, allowlist exempt) | 3b | error | error |
| Language tag on every fence | 4 | warning → error once the backfill lands | error |
| Language tag on every fence | 4 | error | error |
| "Dispatcher", not "ServiceActivator" or "Service Activator", in prose | 5 | error | error |
| `using` directives in C# blocks | 6 | warning, counted | error, unless the block marks its omission `// ...` |
| Version markers on code (❌/✅) | — | **review only** | **review only** |
Expand All @@ -336,6 +340,22 @@ Version markers are the one convention with no rule, and deliberately so: whethe
code blocks differ *by version* is a judgement about meaning, and a regex that guessed
at it would fire on every before/after pair in the repo. It is checked in review.

**`--fix` repairs two of these and refuses the rest.** It retargets a banner whose
*Applies to* is stale against `APPLIES_TO` — which is what makes a version bump one
edit to that tuple plus one command — and tags an untagged fence ```` ```text ````
when nothing in the block looks like code. It rewrites only the version segment, so
the page type and any Prerequisites are out of its reach by construction.

It **never decides a page type.** That is a judgement about what a page is *for*, it
cannot be recovered from the text, and a wrong one is invisible: a page mislabelled
`Reference` reads perfectly and misleads everyone who trusted the label. A banner with
an out-of-vocabulary type gets its version fixed and still fails rule 2, which is the
intended outcome — `--fix` cannot turn a bad page type into a green build.

Where the answer is not unique it says so and changes nothing: a version naming a
product no single `APPLIES_TO` entry covers, and a fence holding anything code-shaped,
where choosing between `csharp`, `bash`, `json` and `yaml` belongs to whoever wrote it.

Rule 5 matches **both spellings** — `ServiceActivator` and `Service Activator`. The
API surface uses the closed form, but prose here uses the open one just as often, and
both are the same V9 term.
Expand Down
2 changes: 1 addition & 1 deletion contents/AsyncAPISupport.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Brighter generates AsyncAPI 3.0 documents by inspecting your registered [subscri
- .NET 8.0 or later
- Two NuGet packages:

```
```bash
dotnet add package Paramore.Brighter.AsyncAPI
dotnet add package Paramore.Brighter.AsyncAPI.NJsonSchema
```
Expand Down
4 changes: 2 additions & 2 deletions contents/AwsScheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Brighter provides two approaches for scheduling with AWS EventBridge Scheduler:

When `UseMessageTopicAsTarget = true` (default), Brighter schedules messages directly to the target SNS topic or SQS queue:

```
```text
Your Code → CommandProcessor.SendAsync(delay, command)
Brighter creates AWS EventBridge Schedule
Expand All @@ -54,7 +54,7 @@ Your Dispatcher → Handler executes

When `UseMessageTopicAsTarget = false` or using request scheduler, Brighter schedules through an intermediate `FireAwsScheduler` message:

```
```text
Your Code → CommandProcessor.SendAsync(delay, command)
Brighter creates AWS EventBridge Schedule
Expand Down
2 changes: 1 addition & 1 deletion contents/AzureScheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Azure Service Bus Scheduler is recommended when:

Brighter uses Azure Service Bus's native `ScheduledEnqueueTimeUtc` property through the `FireAzureScheduler` message approach:

```
```text
Your Code → CommandProcessor.SendAsync(delay, command)
Brighter creates FireAzureScheduler message
Expand Down
4 changes: 2 additions & 2 deletions contents/BrighterSchedulerSupport.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Brighter uses two internal message types for scheduling:

### Scheduling Flow

```
```text
Your Application
CommandProcessor.SendAsync(command, delay)
Expand Down Expand Up @@ -301,7 +301,7 @@ Brighter supports multiple scheduler implementations. Your choice depends on you

### Decision Guide

```
```text
┌─────────────────────────────────────────┐
│ Are you deploying to AWS? │
└──────────────┬──────────────────────────┘
Expand Down
6 changes: 3 additions & 3 deletions contents/CQRSWithBrighterAndDarker.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ When building a CQRS application, you'll use both Brighter and Darker together i

Here's how Brighter and Darker fit together in a typical ASP.NET Core application:

```
```text
┌─────────────────────────────────────────────────────────────┐
│ Web Application (ASP.NET Core) │
├──────────────────────────┬──────────────────────────────────┤
Expand Down Expand Up @@ -537,7 +537,7 @@ public class GetOrderSummaryQueryHandler :

**Scenario:** Commands write to a primary database; queries read from a replica or separate optimized read database

```
```text
┌────────────────┐
│ Commands │
│ (Brighter) │
Expand Down Expand Up @@ -580,7 +580,7 @@ public class GetOrderSummaryQueryHandler :

This advanced pattern stores all state changes as a sequence of events. The query side builds read models (projections) by replaying events.

```
```text
┌────────────────┐
│ Commands │
│ (Brighter) │
Expand Down
2 changes: 1 addition & 1 deletion contents/CloudEventsSupport.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ In binary-mode, CloudEvents attributes are mapped to protocol headers, and the e
- You want to inspect event metadata without deserializing the body

**Example RabbitMQ message with binary CloudEvents:**
```
```text
Headers:
ce_id: "a89b61a2-5c5c-4d7e-8b8f-2e0f9c1d3e4f"
ce_source: "https://example.com/orders"
Expand Down
2 changes: 1 addition & 1 deletion contents/HowServiceActivatorWorks.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The `Message Pump`:

## Dispatcher Architecture

```
```text
External Message Broker
[Dispatcher] (ServiceActivator assembly)
Expand Down
2 changes: 1 addition & 1 deletion contents/InMemoryScheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This simple approach makes it perfect for testing but unsuitable for production

## InMemory Scheduler Architecture

```
```text
Your Code
CommandProcessor.SendAsync(command, delay)
Expand Down
10 changes: 5 additions & 5 deletions contents/PipelineValidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ These checks apply to all Brighter applications, including those that only use t

**Example error messages:**

```
```text
Handler type 'MyNamespace.OrderHandler' is not public — Brighter only supports
public handler types. Make the class public so the pipeline builder can find it

Expand Down Expand Up @@ -76,7 +76,7 @@ These checks apply when you configure outgoing messages with `AddProducers()`.

**Example error messages:**

```
```text
Publication.RequestType is null — Post()/Deposit() will throw ConfigurationException

Publication.RequestType 'MyNamespace.OrderData' does not implement IRequest
Expand All @@ -94,7 +94,7 @@ These checks apply when you configure incoming messages with `AddConsumers()`.

**Example error messages:**

```
```text
Subscription uses Reactor (sync) pump but handler 'OrderHandler' is async —
use Proactor for async handlers

Expand All @@ -113,7 +113,7 @@ The `DescribePipelines()` method logs a structured report showing how your pipel

At `Information` log level, a single summary line is logged:

```
```text
Brighter: 3 handler pipelines, 2 publications, 5 subscriptions configured
```

Expand All @@ -123,7 +123,7 @@ The summary includes counts only for the configuration paths you use. If you don

At `Debug` log level, the report shows the full wiring for each configuration path. Here is an example with all three paths configured:

```
```text
=== Handler Pipelines ===
OrderCreatedHandler (async)
Pipeline: [DeferMessageOnErrorAsync(0)] → [UseResiliencePipelineAsync(1)] → OrderCreatedHandler
Expand Down
8 changes: 4 additions & 4 deletions contents/QueriesAndQueryObjects.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,15 +650,15 @@ Use descriptive, specific names that clearly communicate the query's purpose.
Organize query files in a way that makes them easy to find and maintain:

**Option 1: Queries folder**
```
```text
/Queries
GetOrderQuery.cs
GetCustomerQuery.cs
SearchProductsQuery.cs
```

**Option 2: Feature folders**
```
```text
/Features
/Orders
GetOrderQuery.cs
Expand All @@ -669,7 +669,7 @@ Organize query files in a way that makes them easy to find and maintain:
```

**Option 3: Colocation with handlers**
```
```text
/Orders
/Queries
GetOrderQuery.cs
Expand Down Expand Up @@ -728,7 +728,7 @@ This approach reduces file count and keeps related code together.

For microservices or modular monoliths, consider a shared query library:

```
```text
/MyApp.Contracts
/Queries
GetOrderQuery.cs
Expand Down
2 changes: 1 addition & 1 deletion contents/QueryPipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This approach follows the [Decorator Pattern](https://en.wikipedia.org/wiki/Deco

When you call `IQueryProcessor.ExecuteAsync(query)`, Darker constructs a pipeline of decorators around your query handler based on the attributes you've applied to the handler's `ExecuteAsync` method. The execution flows through each decorator in order before reaching your handler:

```
```text
QueryProcessor.ExecuteAsync(query)
[QueryLogging Decorator - Step 1]
Expand Down
6 changes: 3 additions & 3 deletions contents/ReplayOnSeen.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ which are reserved for workflow orchestration.

When the Inbox recognises a request it has already handled and the action is `Replay`:

```
```text
Duplicate PlaceOrder arrives
Expand Down Expand Up @@ -555,7 +555,7 @@ stores for their schema capability rather than assuming it.
Each message is prefixed with the handler it came from — `Handler 'ProcessPaymentHandler'`.
An unconfigured Inbox renders as `'(none)'` in the first message. The messages read:

```
```text
OnceOnlyAction.Replay requires a causation-tracking inbox, but the configured
inbox 'MyCustomInbox' does not implement IAmACausationTrackingInbox — Replay
cannot find the causation id of the original handling
Expand Down Expand Up @@ -611,7 +611,7 @@ pipeline's context at all and falls back to a throwaway whose Bag never reaches
This one does leave a trace: a single warning, logged once per process the first time a
Replay pipeline hits it.

```
```text
A custom IRequestContext (not a RequestContext) was supplied; the causation id
cannot flow to downstream handlers, so OnceOnlyAction.Replay will be a no-op
```
Expand Down
18 changes: 9 additions & 9 deletions contents/Telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ When Brighter operates as a Dispatcher (message consumer), it creates spans for

### Example Flow

```
```text
Dispatcher Span: "task.commands receive" (Consumer)
└─> Message Translation (sibling)
└─> Command Processor Span: "ProcessTaskCommand send" (Internal)
Expand All @@ -231,15 +231,15 @@ Outbox operations create child spans for database operations:

### Deposit Operation

```
```text
deposit span (Internal)
└─> Transform pipeline spans
└─> Outbox add span (Database)
```

### Clear Operation

```
```text
create/clear span (Internal)
└─> Outbox get span (Database)
└─> Produce message span (Producer)
Expand All @@ -262,7 +262,7 @@ Outbox and Inbox database operations follow [OTel Database Semantic Conventions]

Inbox operations create child spans for deduplication checks:

```
```text
Dispatcher receive span (Consumer)
└─> Message translation
└─> Inbox check span (Database)
Expand All @@ -284,7 +284,7 @@ Transform operations (Claim Check, Compression, Encryption) create child spans f

### Claim Check (S3 Example)

```
```text
deposit span (Internal)
└─> ClaimCheck transform span
└─> S3 put object span (HTTP Client)
Expand All @@ -293,7 +293,7 @@ deposit span (Internal)

### Retrieve Claim

```
```text
Message translation span
└─> RetrieveClaim transform span
└─> S3 get object span (HTTP Client)
Expand All @@ -317,7 +317,7 @@ Brighter automatically propagates trace context across service boundaries using

### Message Headers

```
```text
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: congo=t61rcWkgMzE
```
Expand All @@ -326,7 +326,7 @@ tracestate: congo=t61rcWkgMzE

Brighter participates in existing traces. When called from an ASP.NET controller, the Command Processor span becomes a child of the ASP.NET request span:

```
```text
ASP.NET Request: "POST /orders"
└─> Command Processor: "ProcessOrderCommand send"
└─> Handler: OrderHandler
Expand Down Expand Up @@ -463,7 +463,7 @@ await host.RunAsync();

A complete distributed trace across services:

```
```text
ASP.NET Request (OrderService): "POST /api/orders"
└─> Command Processor: "CreateOrderCommand send"
└─> Handler: CreateOrderCommandHandler
Expand Down
Loading
Loading