Skip to content

Latest commit

 

History

History
1935 lines (1513 loc) · 76.3 KB

File metadata and controls

1935 lines (1513 loc) · 76.3 KB

MidstreamHub — Claude Code Demo Prompt Set & Script

Executive Summary

Audience: CFO (CIO reports to him) Message: Claude Code compresses weeks of work into hours, produces production-grade code, and requires skilled human oversight. Duration: 8 hours prep + 1 hour live demo Repo: ericburcham/claude-code-demo


Architecture Overview

┌─────────────────────────────────────────────────────────────────────┐
│                    Nginx Reverse Proxy (API Gateway)                │
│                    (routes /api/* to services)                      │
├────────────┬────────────┬────────────┬────────────┬─────────────────┤
│  React UI  │ Contracts  │Nominations │ Movements  │  Accounting     │
│  (Vite)    │   API      │   API      │   API      │    API          │
│            │ :5001      │ :5002      │ :5003      │  :5004          │
├────────────┴────────────┴────────────┴────────────┴─────────────────┤
│                     NServiceBus (RabbitMQ Transport)                │
│              ┌──────────────────────────────────────┐               │
│              │  NominationFulfillmentSaga           │               │
│              │  Nom → Mvmt → Measure → Reconcile    │               │
│              └──────────────────────────────────────┘               │
├─────────────────────────────────────────────────────────────────────┤
│  SQL Server (Linux)          │  RabbitMQ                            │
│  ├─ contracts_schema         │  (management UI :15672)              │
│  ├─ nominations_schema       │                                      │
│  ├─ movements_schema         │                                      │
│  └─ accounting_schema        │                                      │
└─────────────────────────────────────────────────────────────────────┘

Systems of Record

SOR Schema Aggregate Root API Port Responsibilities
Contracts contracts Contract 5001 Supply/transport agreements, counterparties, NGL products
Nominations nominations Nomination 5002 Scheduled volumes against contracts
Movements movements Movement 5003 Physical product receipts/deliveries, measurements
Accounting accounting BalancePeriod 5004 Material balance, gain/loss, compensating transactions

NGL Products (Seed Data)

Product Typical UOM Gravity (°API)
Ethane (C2) bbl ~55
Propane (C3) bbl ~30
Normal Butane (nC4) bbl ~25
Isobutane (iC4) bbl ~28
Natural Gasoline (C5+) bbl ~18

Saga: NominationFulfillmentSaga

NominationSubmitted
  → ScheduleMovement (cmd → Movements)
      → MovementScheduled (event)
          → Saga waits for MovementConfirmed
              → MovementConfirmed (event, carries measurement data)
                  → ReconcileVolumes (cmd → Accounting)
                      → VolumesReconciled (event, happy path)
                      OR
                      → VarianceThresholdExceeded (event)
                          → InitiateBalanceAdjustment (cmd → Accounting)
                              → BalanceAdjustmentCompleted (event)
                                  → Saga complete

Solution Structure

claude-code-demo/
├── .github/
│   └── workflows/
│       └── ci.yml
├── .claude/
│   ├── CLAUDE.md
│   ├── settings.json            # enabled plugins config
│   ├── settings.local.json      # permitted tool commands (local)
│   ├── skills/
│   │   ├── clean-architecture.md
│   │   ├── cqrs-mediatr.md
│   │   ├── ddd-patterns.md
│   │   ├── dependency-injection-autofac.md
│   │   ├── k8s-manifests.md
│   │   ├── nservicebus-sagas.md
│   │   └── tdd-nunit.md
│   └── agents/
│       ├── service-scaffolder.md
│       └── frontend-feature.md
├── docker/
│   └── certs/
│       └── RootCert.pem             # Corporate proxy SSL root cert
├── k8s/
│   ├── infrastructure/
│   │   ├── configmap.yaml
│   │   ├── namespace.yaml
│   │   ├── sqlserver.yaml
│   │   ├── rabbitmq.yaml
│   │   └── nginx-ingress.yaml
│   ├── migrations/
│   │   ├── contracts-migrations.yaml
│   │   └── nominations-migrations.yaml
│   └── services/
│       ├── contracts-api.yaml
│       ├── nominations-api.yaml
│       └── web-ui.yaml
├── src/
│   ├── MidstreamHub.sln
│   ├── Directory.Build.props
│   ├── Directory.Packages.props
│   ├── stylecop.json
│   ├── StyleCopAnalyzers.ruleset
│   ├── nuget.config
│   │
│   ├── Shared/
│   │   ├── MidstreamHub.Contracts.Messages/     # NuGet: NServiceBus message contracts
│   │   ├── MidstreamHub.Nominations.Messages/   # NuGet: nomination-specific messages
│   │   ├── MidstreamHub.Domain.Common/          # NuGet: domain primitives
│   │   └── MidstreamHub.Infrastructure.Common/  # NuGet: cross-cutting
│   │
│   ├── Contracts/
│   │   ├── MidstreamHub.Contracts.Api/       # Minimal API host
│   │   ├── MidstreamHub.Contracts.Application/ # CQRS handlers
│   │   ├── MidstreamHub.Contracts.Domain/    # Aggregate, entities, VOs
│   │   ├── MidstreamHub.Contracts.Infrastructure/ # EF Core, repos
│   │   ├── MidstreamHub.Contracts.Migrations/    # Fluent Migrator class lib
│   │   ├── MidstreamHub.Contracts.Migrations.Runner/ # Console app for K8S job
│   │   └── MidstreamHub.Contracts.Tests/     # NUnit + TestContainers
│   │
│   ├── Nominations/
│   │   ├── MidstreamHub.Nominations.Api/
│   │   ├── MidstreamHub.Nominations.Application/
│   │   ├── MidstreamHub.Nominations.Domain/
│   │   ├── MidstreamHub.Nominations.Infrastructure/
│   │   ├── MidstreamHub.Nominations.Migrations/
│   │   ├── MidstreamHub.Nominations.Migrations.Runner/
│   │   └── MidstreamHub.Nominations.Tests/
│   │
│   ├── Movements/                            # DEMO PHASE
│   │   └── (same structure)
│   │
│   ├── Accounting/                           # DEMO PHASE
│   │   └── (same structure)
│   │
│   └── Web/
│       └── midstreamhub-ui/                  # Vite + React + TypeScript
│
├── build/
│   ├── Build.cs                              # Nuke build
│   ├── Configuration.cs
│   ├── _build.csproj
│   └── Directory.Build.props                 # Blocks parent inheritance
│
├── local-nuget/                              # Local folder NuGet feed
├── .nuke/                                    # Nuke build system config
├── Tiltfile
├── Dockerfile.api                            # Multi-stage, shared across .NET services
├── Dockerfile.migrations                     # For migration runner jobs
├── Dockerfile.web                            # Multi-stage for React frontend
├── build.cmd                                 # Nuke build entry (Windows)
├── build.ps1                                 # Nuke build entry (PowerShell)
├── build.sh                                  # Nuke build entry (Unix)
└── LICENSE

Prep / Demo Boundary

PRE-BUILT (8 hours): Prompts P01–P14

  • Repository structure, build, CI
  • CLAUDE.md, skills, plugins, agents
  • All shared NuGet packages
  • Contracts service (full reference implementation)
  • Nominations service + NominationFulfillmentSaga (first step)
  • React UI: Dashboard, Contracts CRUD, Nomination submission
  • K8S infrastructure + Tiltfile
  • Seed data
  • Everything compiles, passes tests, runs in Tilt

LIVE DEMO (1 hour): Prompts D01–D05

  • D01: Movement service via subagents (~10 min)
  • D02: Movement React screen + saga wiring (~10 min)
  • D03: Accounting service via agent teams (~10 min)
  • D04: Reconciliation + compensating transaction + dashboard (~10 min)
  • D05: Push to GitHub, CI triggers (~5 min)
  • Buffer/discussion: ~15 min

CLAUDE.md

The following content should be placed at .claude/CLAUDE.md in the repository root. It is referenced by Prompt P02.

# MidstreamHub — Claude Code Instructions

## Project Overview

MidstreamHub is a midstream oil & gas NGL operations platform built as a distributed
microservice system using .NET 10, NServiceBus, and a React frontend.

## Architecture Principles

- **CLEAN Architecture**: Each service follows CLEAN layering (Api → Application → Domain → Infrastructure)
- **CQRS**: Commands and queries are separate, dispatched via MediatR
- **DDD**: Each System of Record owns its aggregate root, entities, and value objects
- **Schema Isolation**: Each SOR has its own database schema; NO cross-schema queries
- **Value Object Hydration**: Other SOR data is obtained via HTTP API calls, never direct DB access
- **12-Factor App**: Config via environment variables and user-secrets, stateless processes
- **Eventual Consistency**: NServiceBus sagas coordinate cross-SOR workflows

## Technology Stack

| Concern | Technology |
|---------|-----------|
| Runtime | .NET 10 |
| API Style | Minimal APIs |
| DI Container | Autofac (one Module per service layer) |
| Mediator | MediatR |
| Validation | FluentValidation (in MediatR pipeline) |
| ORM | EF Core (no EF migrations) |
| Schema Migrations | Fluent Migrator (isolated class libraries, K8S jobs) |
| Messaging | NServiceBus (RabbitMQ transport, SQL persistence) |
| Testing | NUnit + TestContainers |
| Build | Nuke |
| Logging | Microsoft.Extensions.Logging, JSON console, ConversationId in scope |
| Styling | StyleCop (ruleset in src/) |
| Frontend | Vite + React + TypeScript |
| Package Mgmt | Central package management (Directory.Packages.props) |

## Coding Standards

- All code MUST pass StyleCop analysis using the ruleset at `src/StyleCopAnalyzers.ruleset`
- Do NOT prefix local calls with `this.` (SX1101 enforced)
- Private fields MUST start with underscore `_` (SX1309 enforced)
- Using directives go INSIDE namespace declarations
- Use `Directory.Build.props` for shared project properties
- Use `Directory.Packages.props` for all package version management
- Autofac modules: one per CLEAN layer per service (e.g., `ContractsDomainModule`, `ContractsInfrastructureModule`)
- FluentValidation validators registered via Autofac assembly scanning
- MediatR handlers registered via Autofac assembly scanning
- All API endpoints return RFC 7807 ProblemDetails on error
- All configuration via environment variables; local dev uses user-secrets
- Connection strings use the key pattern: `ConnectionStrings__{ServiceName}`

## NServiceBus Conventions

- Commands: `MidstreamHub.Contracts.Messages.Commands` namespace pattern
- Events: `MidstreamHub.Contracts.Messages.Events` namespace pattern
- Saga data classes live alongside the saga in the Application layer
- ConversationId is automatically propagated; push it into ILogger scope in handlers
- Transport: RabbitMQ with conventional routing
- Persistence: SQL (uses the same SQL Server, separate `nsb` schema)

## File Organization

- Each SOR service follows the structure: `src/{SorName}/MidstreamHub.{SorName}.{Layer}/`
- Shared packages: `src/Shared/MidstreamHub.{PackageName}/`
- K8S manifests: `k8s/{infrastructure|migrations|services}/`
- React app: `src/Web/midstreamhub-ui/`
- Nuke build: `build/Build.cs`

## Docker & Tilt Conventions

- **Local NuGet feed**: `src/nuget.config` references `../local-nuget` (relative path). Inside Docker (`WORKDIR /src`), this resolves to `/local-nuget`. All Dockerfiles must `COPY local-nuget/ /local-nuget/` and Tilt `only` filters must include `"local-nuget"`.
- **Corporate proxy SSL cert (.NET/Debian)**: Containers cannot reach nuget.org without the proxy root cert at `docker/certs/RootCert.pem`. .NET Dockerfiles (Debian-based) must COPY it to `/usr/local/share/ca-certificates/RootCert.crt` and run `update-ca-certificates` BEFORE any `dotnet restore`.
- **Corporate proxy SSL cert (Node/Alpine)**: Alpine images do NOT ship `ca-certificates`, and `apk` cannot install it behind the proxy (chicken-and-egg). Instead, manually append the cert to Alpine's CA bundle and set `NODE_EXTRA_CA_CERTS`:
  ```dockerfile
  COPY docker/certs/RootCert.pem /usr/local/share/ca-certificates/RootCert.crt
  RUN cp /usr/local/share/ca-certificates/RootCert.crt /etc/ssl/certs/RootCert.pem && \
      cat /usr/local/share/ca-certificates/RootCert.crt >> /etc/ssl/certs/ca-certificates.crt
  ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/RootCert.crt
  ```
- Tilt `only` filters must include `"docker/certs"` in ALL `docker_build` blocks (including web-ui).
- **Database creation**: SQL Server does not auto-create the `MidstreamHub` database. Every migration K8S Job must include an `initContainers` entry using `mcr.microsoft.com/mssql-tools:latest` that waits for SQL Server readiness and runs `IF DB_ID('MidstreamHub') IS NULL CREATE DATABASE [MidstreamHub]`. See `k8s/migrations/contracts-migrations.yaml` as the reference template.
- **Tiltfile ACTIVE_SERVICES**: Only add a service when ALL its required artifacts exist (k8s manifests, source projects, Dockerfiles). Missing files cause Tilt load errors.
- **No `--no-restore`**: Do not use `--no-restore` on `dotnet publish` in Dockerfiles to avoid stale NuGet cache issues.
- **Tilt port-forwards**: All .NET containers listen on port 8080 (`ASPNETCORE_URLS=http://+:8080`). Tilt `port_forwards` must map `hostPort:8080`, NOT `hostPort:hostPort`. Example: `port_forwards=["%d:8080" % port]`.

## Nginx Reverse Proxy Conventions

- **No trailing slashes** on `location` directives or `proxy_pass` URIs. Nginx auto-redirects requests missing a trailing slash with a 301 that exposes internal K8S hostnames via the `Location` header.
- **Cache-control headers**: All API `location` blocks must include `add_header Cache-Control "no-cache, no-store, must-revalidate" always;` and `add_header Pragma "no-cache" always;` to prevent browsers from caching stale redirects.
- When adding a new service route, add both an `upstream` block and a `location` block (without trailing slashes) with cache-control headers.

## Vite / React Frontend Conventions

- **Env var naming**: NEVER use `VITE_` prefix for server-side-only variables in `vite.config.ts`. Vite exposes `VITE_`-prefixed vars to client code via `import.meta.env`, which can leak internal hostnames to the browser. Use non-prefixed names (e.g., `API_PROXY_TARGET`) for server-only config.
- **Proxy autoRewrite**: The Vite dev server proxy must include `autoRewrite: true` to rewrite `Location` headers in backend redirect responses, preventing internal K8S hostnames from leaking to the browser.
- **Frontend-backend alignment**: Dropdown/select values in React components must exactly match backend enum values. Reference enums in `src/{Service}/MidstreamHub.{Service}.Domain/Enums/`.

## OpenAPI Conventions

- **Package versions**: Swashbuckle.AspNetCore **10.1.5+** and Microsoft.AspNetCore.OpenApi **10.0.4+** (required for .NET 10; older Swashbuckle versions silently fail)
- **Registration**: Use parameterless `AddSwaggerGen()` — the `OpenApiInfo` type from Microsoft.OpenApi v1.x was removed in v2.x
- **Route prefix**: OpenAPI docs are served at `/openapi` (not `/swagger`). Configure via:
  ```csharp
  app.UseSwagger(c => c.RouteTemplate = "openapi/{documentName}/openapi.json");
  app.UseSwaggerUI(c =>
  {
      c.SwaggerEndpoint("v1/openapi.json", "Service Name");
      c.RoutePrefix = "openapi";
  });
  ```
- **Relative endpoints**: `SwaggerEndpoint` must use **relative** paths — absolute paths cause 404
- **Always enabled**: No `IsDevelopment()` gate — public APIs should have public OpenAPI docs

## When Creating a New Service

1. Create all CLEAN layers (Api, Application, Domain, Infrastructure, Migrations, Migrations.Runner, Tests)
2. Follow the Contracts service as the reference implementation
3. In the Migrations.Runner project, create a custom `IVersionTableMetaData` (extend `DefaultVersionTableMetaData`, override `SchemaName` to match the service's data schema) and register it with `.AddScoped<IVersionTableMetaData, ...>()` in Program.cs. This prevents VersionInfo table collisions in the shared database. See `ContractsVersionTableMetaData.cs` as the reference.
4. Add K8S manifests to `k8s/services/` and `k8s/migrations/`
   - Migration Job must include the `initContainers` wait-for-db pattern (see `contracts-migrations.yaml`)
5. Add Tilt resources to the Tiltfile
   - Include `"docker/certs"` and `"local-nuget"` in all `docker_build` `only` filters
   - Only add to `ACTIVE_SERVICES` when all artifacts are in place
6. Add projects to `MidstreamHub.sln`
7. Register NServiceBus endpoint in the Api project
8. Add API route prefix: `/api/{sor-name}/`
9. Add nginx ingress route (upstream + location blocks WITHOUT trailing slashes, with cache-control headers)
10. Run `nuke pack` to publish shared packages to local feed if contracts changed

## Testing Conventions

- Unit tests: test domain logic and MediatR handlers in isolation
- Integration tests: use TestContainers for SQL Server and RabbitMQ
- Fluent Migrator migrations: tested by applying to a TestContainers SQL Server instance
- Test project per SOR: `MidstreamHub.{SorName}.Tests`

Claude Code Skills

The following skill files should be placed in .claude/skills/. They are created by Prompt P02.

Note: Only a subset of core skills are created manually. After P02, run the claude-automation-recommender skill from the claude-code-setup plugin to recommend additional skills based on the repository contents. This typically adds dependency-injection-autofac.md and k8s-manifests.md.

.claude/skills/clean-architecture.md

# CLEAN Architecture Skill

When creating or modifying service layers, follow this dependency rule:

  Domain → (no dependencies)
  Application → Domain
  Infrastructure → Application, Domain
  Api → Application, Domain, Infrastructure

## Layer Responsibilities

### Domain
- Aggregate roots, entities, value objects, domain events
- No framework dependencies (no EF Core, no MediatR)
- Rich domain model with behavior, not anemic

### Application
- MediatR command/query handlers
- FluentValidation validators
- DTOs for API responses
- Saga definitions (NServiceBus)
- Interfaces for infrastructure concerns (repositories, external API clients)

### Infrastructure
- EF Core DbContext and entity configurations
- Repository implementations
- External API HTTP clients (for value object hydration from other SORs)
- Autofac module registering all infrastructure services

### Api
- Minimal API endpoint definitions grouped by feature
- Autofac module composition (registers all layer modules)
- Program.cs: host builder, NServiceBus config, middleware pipeline
- No business logic — delegate everything to MediatR

.claude/skills/cqrs-mediatr.md

# CQRS with MediatR Skill

## Commands
- Inherit from `IRequest<TResponse>` where TResponse is the result type
- One handler per command in the Application layer
- Validators in the same folder as the command
- Commands mutate state; always return a result indicating success/failure

## Queries
- Inherit from `IRequest<TResponse>`
- Handlers can use read-optimized paths (e.g., Dapper, raw SQL) if needed
- Queries NEVER mutate state

## Pipeline
- `FluentValidationBehavior<TRequest, TResponse>` registered in the MediatR pipeline
- Validation runs automatically before the handler
- Validation failures return RFC 7807 ProblemDetails

## Registration
- Use Autofac `RegisterMediatR()` extension with assembly scanning
- Register FluentValidation validators via `AsClosedTypesOf(typeof(IValidator<>))`

## Naming
- Commands: `Create{Entity}Command`, `Update{Entity}Command`
- Queries: `Get{Entity}Query`, `Get{Entity}ListQuery`
- Handlers: `{CommandOrQuery}Handler`
- Validators: `{CommandOrQuery}Validator`

.claude/skills/nservicebus-sagas.md

# NServiceBus Sagas Skill

## Saga Design Rules
- Saga data class must inherit `ContainSagaData`
- Map incoming messages to saga data using `ConfigureHowToFindSaga`
- Use a unique business identifier as the correlation property (e.g., NominationId)
- Call `MarkAsComplete()` when the workflow is done

## Message Types
- **Commands**: sent to a specific endpoint, represent intent (e.g., `ScheduleMovement`)
- **Events**: published, represent something that happened (e.g., `MovementConfirmed`)
- Commands and events live in `MidstreamHub.Contracts` shared NuGet package

## Compensating Transactions
- When a saga detects a failure or threshold breach, send a compensating command
- Compensating handlers should be idempotent
- Log compensation actions clearly for auditability

## ConversationId
- Automatically flows across all messages in a conversation
- Push into ILogger scope at the start of every handler:
  `using (logger.BeginScope(new Dictionary<string, object> { ["ConversationId"] = context.MessageHeaders[Headers.ConversationId] }))`

## Testing
- Use NServiceBus.Testing package for saga unit tests
- Assert that expected messages are sent/published at each saga step

.claude/skills/ddd-patterns.md

# DDD Patterns Skill

## Aggregate Root
- Single entry point for all modifications to the aggregate
- Enforce invariants in the aggregate root's methods
- Raise domain events from the aggregate root

## Value Objects
- Immutable, compared by value
- Use records in C# for value objects
- Value objects from OTHER SORs are hydrated via API calls, never DB joins
- Cache hydrated value objects appropriately
- Value objects used in EF Core entity configs (especially when some properties are `.Ignore()`d) need a **private parameterless constructor** for EF materialization. Place it AFTER the public constructor (StyleCop SA1202).
  ```csharp
  public MyValueObject(string a, string b) { A = a; B = b; }
  private MyValueObject() { A = null!; B = null!; }
  ```

## Entities
- Have identity (Id property)
- Belong to an aggregate; only accessible through the aggregate root
- Mutable, but changes go through aggregate root methods

## Repository Pattern
- One repository per aggregate root
- Repository interface in Domain layer
- Repository implementation in Infrastructure layer
- No generic repository — explicit methods per use case

.claude/skills/tdd-nunit.md

# TDD with NUnit Skill

## Test Structure
- Arrange / Act / Assert pattern
- One assert per test (prefer)
- Descriptive test names: `MethodName_Scenario_ExpectedResult`

## Test Categories
- `[Category("Unit")]` for pure logic tests
- `[Category("Integration")]` for tests requiring TestContainers

## TestContainers Usage
- SQL Server: `new MsSqlContainer("mcr.microsoft.com/mssql/server:2022-latest")`
- RabbitMQ: `new RabbitMqContainer("rabbitmq:3-management")`
- Start containers in `[OneTimeSetUp]`, dispose in `[OneTimeTearDown]`

## Migration Testing
- Apply Fluent Migrator migrations to a TestContainers SQL Server
- Verify schema exists and matches expected structure
- Test both Up and Down migrations

Claude Code Settings

Plugin configuration goes in .claude/settings.json. Created/updated during Prompt P02.

{
  "enabledPlugins": {
    "frontend-design@claude-plugins-official": true,
    "superpowers@claude-plugins-official": true,
    "code-review@claude-plugins-official": true,
    "feature-dev@claude-plugins-official": true,
    "code-simplifier@claude-plugins-official": true,
    "ralph-loop@claude-plugins-official": true,
    "commit-commands@claude-plugins-official": true,
    "claude-md-management@claude-plugins-official": true,
    "serena@claude-plugins-official": true,
    "csharp-lsp@claude-plugins-official": true
  }
}

Claude Code Agents

.claude/agents/service-scaffolder.md

# Service Scaffolder Agent

You are a subagent responsible for scaffolding a new CLEAN architecture microservice
for MidstreamHub. You receive a service name and aggregate root description.

## Inputs
- Service name (e.g., "Movements")
- Aggregate root name (e.g., "Movement")
- Key entity descriptions
- Database schema name

## Steps
1. Create all project folders following the Contracts service structure exactly
2. Create .csproj files with correct project references and central package versions
3. Create the Domain layer: aggregate root, entities, value objects, repository interface
4. Create the Application layer: MediatR commands/queries/handlers, FluentValidation validators, DTOs
5. Create the Infrastructure layer: EF Core DbContext, entity configs, repository impl, Autofac module
6. Create the Api layer: Minimal API endpoints, Program.cs with NServiceBus + Autofac, Autofac root module
7. Create Migrations class library with initial migration
8. Create Migrations.Runner console app with custom `IVersionTableMetaData` (extend `DefaultVersionTableMetaData`, override `SchemaName` to the service's schema) registered via `.AddScoped<IVersionTableMetaData, ...>()`. See `ContractsVersionTableMetaData.cs` as reference.
9. Create Tests project with at least one unit test and one integration test
10. Add all projects to MidstreamHub.sln
11. Add K8S manifests (service deployment + migration job)
12. Add Tilt resources to Tiltfile
13. Add nginx ingress route (upstream + location blocks WITHOUT trailing slashes, with cache-control headers)
14. Verify: `dotnet build src/MidstreamHub.sln`

.claude/agents/frontend-feature.md

# Frontend Feature Agent

You are a subagent responsible for adding a new feature/page to the MidstreamHub
React frontend.

## Inputs
- Feature name and description
- API endpoints to consume
- Expected UI behavior

## Steps
1. Create a new page component in `src/Web/midstreamhub-ui/src/pages/`
2. Create API client functions in `src/Web/midstreamhub-ui/src/api/`
3. Add TypeScript types matching the API DTOs
4. Add route to the router configuration
5. Add navigation link to the sidebar/header
6. Follow existing page patterns for layout, error handling, loading states
7. Use the project's established UI component library and styling approach
8. **Verify enum alignment**: All dropdown/select values MUST exactly match backend enum values. Check `src/{Service}/MidstreamHub.{Service}.Domain/Enums/` for the authoritative values.

Prep Phase Prompts (P01–P14)

These prompts are executed sequentially during the 8-hour prep window. Each prompt is self-contained with enough context for Claude Code to execute without ambiguity.


P01: Repository Initialization & Solution Structure

Estimated time: 20–30 minutes Visual checkpoint: dotnet build succeeds on empty solution

Initialize the MidstreamHub repository. This is a midstream oil & gas NGL operations
platform built as a distributed .NET 10 microservice system.

## Tasks

1. Initialize a git repository in the current directory.

2. Create a .gitignore appropriate for .NET, Node.js, Rider/VS, and Nuke.

3. Create the solution file at src/MidstreamHub.sln (empty for now).

4. Create src/Directory.Build.props with:
   - TargetFramework: net10.0
   - Nullable: enable
   - ImplicitUsings: enable
   - TreatWarningsAsErrors: true
   - CodeAnalysisRuleSet pointing to StyleCopAnalyzers.ruleset
   - Include StyleCop.Analyzers as a shared analyzer reference

5. Create src/Directory.Packages.props with ManagePackageVersionsCentrally=true
   and these initial package versions (use latest stable versions for .NET 10):
   - Autofac.Extensions.DependencyInjection
   - MediatR.Extensions.Autofac.DependencyInjection
   - FluentMigrator
   - FluentMigrator.Runner
   - FluentMigrator.Runner.SqlServer
   - FluentValidation
   - FluentValidation.DependencyInjectionExtensions
   - MediatR
   - Microsoft.EntityFrameworkCore
   - Microsoft.EntityFrameworkCore.SqlServer
   - Microsoft.Data.SqlClient
   - NServiceBus
   - NServiceBus.Extensions.Hosting
   - NServiceBus.RabbitMQ
   - NServiceBus.Persistence.Sql
   - NServiceBus.Testing
   - NUnit
   - NUnit3TestAdapter
   - Microsoft.NET.Test.Sdk
   - Testcontainers
   - Testcontainers.MsSql
   - Testcontainers.RabbitMq
   - StyleCop.Analyzers
   - Swashbuckle.AspNetCore
   - Nuke.Common
   - Microsoft.AspNetCore.OpenApi

6. Create the src/nuget.config with two package sources:
   - Local folder: ../local-nuget
   - nuget.org: https://api.nuget.org/v3/index.json

7. Create the local-nuget/ directory with a placeholder .gitkeep file.

8. Create the directory structure for the first service (Contracts) as empty folders:
   src/Contracts/MidstreamHub.Contracts.Api/
   src/Contracts/MidstreamHub.Contracts.Application/
   src/Contracts/MidstreamHub.Contracts.Domain/
   src/Contracts/MidstreamHub.Contracts.Infrastructure/
   src/Contracts/MidstreamHub.Contracts.Migrations/
   src/Contracts/MidstreamHub.Contracts.Migrations.Runner/
   src/Contracts/MidstreamHub.Contracts.Tests/

9. Create the same directory structure for Nominations.

10. Create the shared package directories:
    src/Shared/MidstreamHub.Contracts.Messages/
    src/Shared/MidstreamHub.Nominations.Messages/
    src/Shared/MidstreamHub.Domain.Common/
    src/Shared/MidstreamHub.Infrastructure.Common/

11. Create the React app directory: src/Web/midstreamhub-ui/ (we will scaffold it later)

12. Create the k8s directory structure:
    k8s/infrastructure/
    k8s/migrations/
    k8s/services/

13. Create the build/ directory for Nuke.

14. Create the docker/certs/ directory and place the corporate proxy root certificate
    at `docker/certs/RootCert.pem`. This cert is required by all Dockerfiles to reach
    nuget.org and npm behind the corporate proxy.

15. Commit with message: "chore: initialize solution structure and central package management"

P02: CLAUDE.md, Skills, Plugins, and Agents

Estimated time: 15–20 minutes Visual checkpoint: .claude/ directory populated, plugins enabled

Set up Claude Code configuration for the MidstreamHub repository. Create all files
exactly as specified — these define the behavioral rules and patterns for all
subsequent prompts.

## Tasks

1. Create .claude/CLAUDE.md with the project instructions.
   (Paste the full CLAUDE.md content from the "CLAUDE.md" section of this document.)

2. Create the following skill files in .claude/skills/:
   (Paste each skill file from the "Claude Code Skills" section of this document.)
   - clean-architecture.md
   - cqrs-mediatr.md
   - nservicebus-sagas.md
   - ddd-patterns.md
   - tdd-nunit.md

3. Create .claude/settings.json with the enabled plugins configuration.
   (Paste from the "Claude Code Settings" section of this document.)

4. Create the agent definitions in .claude/agents/:
   - service-scaffolder.md
   - frontend-feature.md

5. Commit with message: "chore: add Claude Code configuration, skills, plugins, and agents"

After this is committed, run the claude-automation-recommender from the
claude-code-setup plugin to see if additional skills are recommended based on
the repository contents. Add any recommended skills that are relevant.

P03: Nuke Build

Estimated time: 20–30 minutes Visual checkpoint: nuke --help shows all targets

Set up a Nuke build system for MidstreamHub. The build should support the full
development lifecycle and CI/CD pipeline.

## Tasks

1. Initialize Nuke in the repository root using `nuke :setup` if the tool is
   available, or manually create the build project at build/Build.cs.

2. Create build/Build.cs with these targets (in dependency order):
   - Clean: clean all bin/obj directories under src/
   - Restore: dotnet restore src/MidstreamHub.sln
   - Compile: dotnet build src/MidstreamHub.sln --no-restore
   - Test: dotnet test src/MidstreamHub.sln --no-build --filter Category=Unit
   - IntegrationTest: dotnet test src/MidstreamHub.sln --no-build --filter Category=Integration
   - Pack: dotnet pack the shared NuGet packages to local-nuget/ folder
   - DockerBuild: build Docker images for each API service and migration runner

3. The default target should be: Clean → Restore → Compile → Test

4. Use Nuke parameters:
   - Configuration (default: Debug for local, Release for CI)
   - DockerRegistry (default: empty for local)

5. Create the build/_build.csproj with the Nuke.Common dependency.

6. Ensure `nuke compile` succeeds (it will build the empty solution, which is fine).

7. Commit with message: "build: add Nuke build system with full target chain"

P04: Shared NuGet Packages

Estimated time: 30–40 minutes Visual checkpoint: Packages appear in local-nuget/ after nuke pack

Create the shared NuGet packages for MidstreamHub. These define the contracts,
domain primitives, and infrastructure utilities shared across all microservices.

## Package 1: MidstreamHub.Contracts.Messages

This package contains all NServiceBus message contracts (commands and events) and
shared DTOs used across service boundaries.

Create the project and add:

### Commands (namespace MidstreamHub.Contracts.Messages.Commands)
- ScheduleMovement: NominationId (Guid), ContractId (Guid), ProductCode (string),
  ScheduledVolume (decimal), UnitOfMeasure (string), ScheduledDate (DateTime)
- ConfirmMovement: MovementId (Guid), ActualVolume (decimal), MeasurementTimestamp (DateTime)
- ReconcileVolumes: MovementId (Guid), NominationId (Guid), PlannedVolume (decimal),
  ActualVolume (decimal), UnitOfMeasure (string), PeriodStart (DateTime), PeriodEnd (DateTime)
- InitiateBalanceAdjustment: BalancePeriodId (Guid), VarianceVolume (decimal),
  UnitOfMeasure (string), Reason (string)

### Events (namespace MidstreamHub.Contracts.Messages.Events)
- NominationSubmitted: NominationId (Guid), ContractId (Guid), ProductCode (string),
  NominatedVolume (decimal), UnitOfMeasure (string), NominationDate (DateTime)
- MovementScheduled: MovementId (Guid), NominationId (Guid)
- MovementConfirmed: MovementId (Guid), NominationId (Guid), ActualVolume (decimal),
  UnitOfMeasure (string), MeasurementTimestamp (DateTime)
- VolumesReconciled: BalancePeriodId (Guid), NominationId (Guid), Variance (decimal)
- VarianceThresholdExceeded: BalancePeriodId (Guid), NominationId (Guid),
  Variance (decimal), ThresholdPercent (decimal)
- BalanceAdjustmentCompleted: BalancePeriodId (Guid), AdjustmentVolume (decimal)

All message classes should be simple POCOs with public get/set properties.
Mark commands with NServiceBus.ICommand and events with NServiceBus.IEvent.

## Package 2: MidstreamHub.Domain.Common

Shared domain primitives and base classes:
- EntityBase<TId>: abstract base with Id, CreatedAt, UpdatedAt
- AggregateRoot<TId>: extends EntityBase, adds domain event collection
- ValueObject: abstract base with value equality
- UnitOfMeasure: value object (Code, Name, ConversionFactorToBbl)
- ProductCode: value object (Code, Name, DefaultUnitOfMeasure)
- Volume: value object (Amount, UnitOfMeasure) with arithmetic operators
- CounterpartyReference: value object (Id, Name) for cross-SOR hydration
- ContractReference: value object (Id, ContractNumber) for cross-SOR hydration
- IRepository<T> interface with GetByIdAsync, AddAsync, UpdateAsync, DeleteAsync

## Package 3: MidstreamHub.Infrastructure.Common

Cross-cutting infrastructure:
- ConversationIdMiddleware: extracts NServiceBus ConversationId from message headers,
  pushes into ILogger scope
- ProblemDetailsFactory: creates RFC 7807 ProblemDetails responses
- FluentValidationBehavior<TRequest, TResponse>: MediatR pipeline behavior
- ServiceCollectionExtensions: helper methods for common registrations
- HttpClientExtensions: typed methods for calling other SOR APIs to hydrate value objects
- CorrelationIdDelegatingHandler: propagates correlation ID on outbound HTTP calls

## Package 4: MidstreamHub.Nominations.Messages

This package contains nomination-specific NServiceBus message contracts that are
published by the Nominations service and consumed by other services.

Create the project with namespace `MidstreamHub.Nominations.Messages`.
Message types will be added as the Nominations service is built in P10.

After creating all four packages:
1. Add them to the solution file
2. Run `nuke pack` to publish to local-nuget/
3. Verify the .nupkg files exist in local-nuget/
4. Commit with message: "feat: add shared NuGet packages (Contracts.Messages, Nominations.Messages, Domain.Common, Infrastructure.Common)"

P05: Kubernetes Infrastructure Manifests

Estimated time: 20–30 minutes Visual checkpoint: kubectl apply succeeds for infrastructure resources

Create Kubernetes manifests for the infrastructure dependencies that MidstreamHub
services rely on. All dependencies run as off-the-shelf containers in the local
K8S cluster.

## Tasks

### k8s/infrastructure/namespace.yaml
- Create a `midstreamhub` namespace for all resources

### k8s/infrastructure/sqlserver.yaml
- Image: mcr.microsoft.com/mssql/server:2022-latest
- Environment: ACCEPT_EULA=Y, MSSQL_SA_PASSWORD from a Secret
- Port: 1433
- Service type: ClusterIP
- PersistentVolumeClaim for /var/opt/mssql (1Gi is fine for demo)
- Create the Secret with SA_PASSWORD=MidstreamHub_Dev123!

### k8s/infrastructure/rabbitmq.yaml
- Image: rabbitmq:3-management
- Ports: 5672 (AMQP), 15672 (management UI)
- Service type: ClusterIP
- Default guest/guest credentials are fine for demo
- PersistentVolumeClaim for /var/lib/rabbitmq (1Gi)

### k8s/infrastructure/nginx-ingress.yaml
- Use a plain nginx:alpine container as a reverse proxy (NOT an Ingress Controller)
- Mount a full `nginx.conf` via ConfigMap at `/etc/nginx/nginx.conf` (subPath mount)
- Use `upstream` blocks for DNS-resilient backend resolution
- Include a `/health` endpoint with liveness/readiness/startup probes
- Only include upstreams for services that exist (contracts, nominations); add others when implemented
- **NO trailing slashes** on `location` directives or `proxy_pass` URIs — nginx auto-redirects when trailing-slash locations receive requests without the slash, exposing internal K8S hostnames via 301 Location headers
- Add `Cache-Control: no-cache, no-store, must-revalidate` and `Pragma: no-cache` headers to all API location blocks to prevent browsers from caching stale redirects
- Routes:
  - /api/contracts → contracts-api:5001
  - /api/nominations → nominations-api:5002
  - /health → returns 200 "healthy"
  - / → returns 404 JSON error

### k8s/infrastructure/configmap.yaml
- Shared ConfigMap for connection string templates:
  - SqlServer__Host=sqlserver.midstreamhub.svc.cluster.local
  - SqlServer__Port=1433
  - RabbitMQ__Host=rabbitmq.midstreamhub.svc.cluster.local
  - RabbitMQ__Port=5672

All resources should be in the `midstreamhub` namespace.

Commit with message: "infra: add K8S manifests for SQL Server, RabbitMQ, and nginx ingress"

P06: Tiltfile

Estimated time: 20–30 minutes Visual checkpoint: tilt up launches infrastructure, dashboard shows resources

Create a Tiltfile for local development of MidstreamHub. The Tiltfile should
orchestrate all infrastructure, services, and the React frontend with live reload.

## Requirements

1. Apply infrastructure K8S manifests first (namespace, sqlserver, rabbitmq, nginx).

2. For each .NET API service (contracts, nominations — and later movements, accounting):
   - Build the Docker image using Dockerfile.api with build arg SERVICE_NAME
   - Include `"docker/certs"` and `"local-nuget"` in all `docker_build` `only` filters
   - Deploy using the K8S manifest from k8s/services/
   - Port-forward to the individual service port for direct debugging
   - Enable live_update for C# source changes

3. For migration jobs:
   - Build using Dockerfile.migrations with build arg SERVICE_NAME
   - Include `"docker/certs"` and `"local-nuget"` in all `docker_build` `only` filters
   - Run as K8S jobs from k8s/migrations/
   - Jobs should run after SQL Server is healthy

4. For the React frontend:
   - Build Docker image using Dockerfile.web
   - Include `"docker/certs"` in the `docker_build` `only` filter
   - Deploy using k8s/services/web-ui.yaml
   - Port-forward :3000
   - Enable live_update for TypeScript/React source changes

5. Create Dockerfile.api as a multi-stage build (.NET SDK 10.0 / ASP.NET 10.0):
   - Trust corporate proxy cert (`docker/certs/RootCert.pem`) via `update-ca-certificates` BEFORE `dotnet restore`
   - Copy `local-nuget/` to `/local-nuget/` for local NuGet feed access
   - Stage 1: sdk image, copy sln + all csproj files, restore
   - Stage 2: copy source, build specific service project
   - Stage 3: runtime image, copy published output, listen on port 8080
   - Accept SERVICE_NAME build arg to select which Api project to build
   - Do NOT use `--no-restore` on `dotnet publish`

6. Create Dockerfile.migrations similarly for migration runners (same cert + local-nuget setup).

7. Create Dockerfile.web for the Vite React app (node:22-alpine base):
   - Both build and dev stages must trust the corporate proxy cert BEFORE `npm ci`:
     ```dockerfile
     COPY docker/certs/RootCert.pem /usr/local/share/ca-certificates/RootCert.crt
     RUN cp /usr/local/share/ca-certificates/RootCert.crt /etc/ssl/certs/RootCert.pem && \
         cat /usr/local/share/ca-certificates/RootCert.crt >> /etc/ssl/certs/ca-certificates.crt
     ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/RootCert.crt
     ```
   - Stage 1 (build): node image, trust cert, install deps, build
   - Stage 2 (production): nginx:alpine, serve static files
   - Stage 3 (dev): node image, trust cert, install deps, `npm run dev -- --host 0.0.0.0 --port 3000`

8. Port mappings for Tilt (host:container):
   - SQL Server: 1433:1433
   - RabbitMQ AMQP: 5672:5672
   - RabbitMQ Management: 15672:15672
   - Contracts API: 5001:8080 (all .NET containers listen on 8080 via ASPNETCORE_URLS)
   - Nominations API: 5002:8080
   - Movements API: 5003:8080 (placeholder for demo phase)
   - Accounting API: 5004:8080 (placeholder for demo phase)
   - Web UI: 3000:3000
   - Nginx Ingress: 8080:8080

9. Add resource dependencies so that:
   - SQL Server starts first
   - Migration jobs wait for SQL Server
   - API services wait for migrations AND RabbitMQ
   - Nginx Ingress waits for API services (contracts-api, nominations-api)
   - Web UI can start independently

10. Add Tilt buttons/options for:
    - Rebuilding the Nuke build
    - Re-running migrations

Commit with message: "infra: add Tiltfile with Dockerfiles for local K8S development"

P07: Contracts Service — Reference Implementation

Estimated time: 60–90 minutes Visual checkpoint: curl localhost:5001/api/contracts returns JSON, Tilt logs show startup

Build the Contracts service as the REFERENCE IMPLEMENTATION for all MidstreamHub
services. Every subsequent service will follow the exact patterns established here.
Consult the CLAUDE.md and all skills in .claude/skills/ before beginning.

This service is the System of Record for supply/transport agreements, counterparties,
and NGL products in a midstream oil & gas context.

## Domain Model (MidstreamHub.Contracts.Domain)

### Aggregate Root: Contract
- Id: Guid
- ContractNumber: string (unique, e.g., "CTR-2025-001")
- Counterparty: Counterparty entity
- Product: ProductCode value object (from Domain.Common)
- ContractType: enum (Supply, Transport, Exchange)
- EffectiveDate: DateTime
- ExpirationDate: DateTime
- MaxVolume: Volume value object (from Domain.Common)
- Status: enum (Draft, Active, Suspended, Expired)
- Methods: Activate(), Suspend(), IsEffective(DateTime)

### Entity: Counterparty
- Id: Guid
- Name: string
- ShortCode: string (e.g., "ACME")
- IsActive: bool

### Entity: Product (reference data, not the value object)
- Id: Guid
- Code: string (e.g., "C3" for Propane)
- Name: string
- DefaultUnitOfMeasure: string (e.g., "bbl")
- ApiGravity: decimal

## Application Layer (MidstreamHub.Contracts.Application)

### Commands
- CreateContractCommand + Handler + Validator
- ActivateContractCommand + Handler + Validator
- SuspendContractCommand + Handler + Validator

### Queries
- GetContractByIdQuery + Handler
- GetContractListQuery + Handler (with optional status filter)
- GetProductListQuery + Handler
- GetCounterpartyListQuery + Handler

### DTOs
- ContractDto, CounterpartyDto, ProductDto

## Infrastructure Layer (MidstreamHub.Contracts.Infrastructure)

### EF Core
- ContractsDbContext with schema "contracts"
- Entity configurations for Contract, Counterparty, Product
- ContractRepository implementing IContractRepository from Domain

### Autofac Module
- ContractsInfrastructureModule: registers DbContext, repositories

## Api Layer (MidstreamHub.Contracts.Api)

### Endpoints (all under /api/contracts/)
- GET / — list contracts (optional ?status= filter)
- GET /{id} — get contract by ID
- POST / — create contract
- PUT /{id}/activate — activate contract
- PUT /{id}/suspend — suspend contract
- GET /products — list NGL products
- GET /counterparties — list counterparties

### Program.cs
- Configure Autofac as the DI container
- Register MediatR with Autofac
- Register FluentValidation pipeline behavior
- Configure NServiceBus endpoint "MidstreamHub.Contracts":
  - RabbitMQ transport
  - SQL persistence (connection string from env var)
  - Enable installers for development
- Configure Microsoft.Extensions.Logging with JSON console
- Add OpenAPI docs at `/openapi` (use parameterless `AddSwaggerGen()`, configure `RoutePrefix = "openapi"`)
- Map Minimal API endpoints
- Global exception handler returning ProblemDetails

### Autofac Root Module
- Load ContractsDomainModule (if any registrations)
- Load ContractsApplicationModule (MediatR, validators)
- Load ContractsInfrastructureModule (EF Core, repos)

## Testing (MidstreamHub.Contracts.Tests)

- Unit test: CreateContractCommandHandler creates a contract correctly
- Unit test: CreateContractCommandValidator rejects invalid data
- Integration test: ContractsDbContext can save and retrieve a Contract using TestContainers
- Integration test: Fluent Migrator migrations apply cleanly to a fresh SQL Server

## Final Steps
1. Add all projects to MidstreamHub.sln
2. Create k8s/services/contracts-api.yaml deployment + service
3. Verify `dotnet build src/MidstreamHub.sln` succeeds
4. Verify Tilt picks up the new service
5. Commit with message: "feat: implement Contracts service (reference implementation)"

P08: Contracts Fluent Migrator Migrations

Estimated time: 20–30 minutes Visual checkpoint: Migration job runs in Tilt, tables exist in SQL Server

Create the Fluent Migrator migrations for the Contracts service schema.

## MidstreamHub.Contracts.Migrations (class library)

Create migrations in the `contracts` schema:

### Migration_001_CreateContractsSchema
- Create schema `contracts` if not exists

### Migration_002_CreateCounterpartiesTable
- Table: contracts.Counterparties
- Columns: Id (uniqueidentifier PK), Name (nvarchar 200), ShortCode (nvarchar 10 unique),
  IsActive (bit default true), CreatedAt (datetime2), UpdatedAt (datetime2)

### Migration_003_CreateProductsTable
- Table: contracts.Products
- Columns: Id (uniqueidentifier PK), Code (nvarchar 10 unique), Name (nvarchar 100),
  DefaultUnitOfMeasure (nvarchar 10), ApiGravity (decimal 18,4),
  CreatedAt (datetime2), UpdatedAt (datetime2)

### Migration_004_CreateContractsTable
- Table: contracts.Contracts
- Columns: Id (uniqueidentifier PK), ContractNumber (nvarchar 50 unique),
  CounterpartyId (uniqueidentifier FK → Counterparties), ProductCode (nvarchar 10),
  ContractType (int), EffectiveDate (datetime2), ExpirationDate (datetime2),
  MaxVolumeAmount (decimal 18,4), MaxVolumeUom (nvarchar 10),
  Status (int), CreatedAt (datetime2), UpdatedAt (datetime2)

All migrations must implement both Up() and Down() methods.

## MidstreamHub.Contracts.Migrations.Runner (console app)

- Accepts connection string from environment variable ConnectionStrings__Contracts
- Runs all migrations from the Migrations assembly
- Logs migration progress
- Returns exit code 0 on success, 1 on failure

## K8S Migration Job (k8s/migrations/contracts-migrations.yaml)

- Uses the migrations runner Docker image
- Runs as a K8S Job with backoffLimit: 3
- Gets connection string from the shared ConfigMap + Secret

## Update Tiltfile

- Add the migration job resource
- Ensure it depends on SQL Server being healthy
- Ensure Contracts API depends on migration job completion

## Tests

- Integration test: apply all migrations to a TestContainers SQL Server, verify schema

Commit with message: "feat: add Contracts schema migrations with Fluent Migrator"

P09: Seed Data

Estimated time: 15–20 minutes Visual checkpoint: API returns seeded products and counterparties

Add seed data to the Contracts service so the system has reference data on startup.

## Tasks

1. Create a data seeder in the Contracts Infrastructure layer that runs on application
   startup (after migrations). Use EF Core to insert data if it doesn't exist.

2. Seed NGL Products:
   | Code | Name | Default UOM | API Gravity |
   |------|------|-------------|-------------|
   | C2 | Ethane | bbl | 55.0 |
   | C3 | Propane | bbl | 30.0 |
   | NC4 | Normal Butane | bbl | 25.0 |
   | IC4 | Isobutane | bbl | 28.0 |
   | C5+ | Natural Gasoline | bbl | 18.0 |

3. Seed Counterparties:
   | ShortCode | Name |
   |-----------|------|
   | ACME | Acme Energy Partners |
   | BASIN | Basin Midstream LLC |
   | SUMMIT | Summit Pipeline Co. |
   | EAGLE | Eagle NGL Processing |

4. Seed one sample Contract:
   - ContractNumber: CTR-2025-001
   - Counterparty: ACME
   - Product: C3 (Propane)
   - Type: Supply
   - Effective: 2025-01-01
   - Expiration: 2025-12-31
   - Max Volume: 50,000 bbl
   - Status: Active

5. The seeder should be idempotent (check before inserting).

6. Verify via Tilt: service starts, seed data is accessible via GET /api/contracts/products

Commit with message: "feat: add seed data for NGL products, counterparties, and sample contract"

P10: Nominations Service

Estimated time: 45–60 minutes Visual checkpoint: POST /api/nominations creates a nomination, publishes NominationSubmitted

Build the Nominations service following the exact patterns established in the
Contracts reference implementation. Consult the Contracts service source code
for every structural decision.

## Domain Model

### Aggregate Root: Nomination
- Id: Guid
- NominationNumber: string (auto-generated, e.g., "NOM-2025-001")
- ContractReference: ContractReference value object (hydrated from Contracts API)
- ProductCode: ProductCode value object
- NominatedVolume: Volume value object
- NominationDate: DateTime
- ScheduledDeliveryDate: DateTime
- Status: enum (Submitted, Scheduled, InProgress, Completed, Rejected)
- Methods: Submit(), MarkScheduled(), MarkInProgress(), Complete(), Reject(string reason)

## Application Layer

### Commands
- CreateNominationCommand + Handler + Validator
  - Handler must call the Contracts API to validate the contract exists and is active
  - Handler must verify nominated volume doesn't exceed contract max volume
  - On success, publish NominationSubmitted event via NServiceBus

### Queries
- GetNominationByIdQuery + Handler
- GetNominationListQuery + Handler (filterable by status, contract)

### NServiceBus Handlers
- (none yet — this service primarily publishes events, the saga lives here too)

### Value Object Hydration
- Create an IContractsApiClient in Application layer interface
- Implement in Infrastructure using HttpClient to call Contracts API
- Register in Autofac module

## Infrastructure

- NominationsDbContext with schema "nominations"
- Entity configs, repository
- HttpClient registration for Contracts API calls

## Migrations (same pattern as Contracts)
- Schema: nominations
- Table: nominations.Nominations with all fields
- Migration Runner console app
- K8S migration job

## Api Endpoints (under /api/nominations/)
- GET / — list nominations
- GET /{id} — get nomination by ID
- POST / — create nomination (this triggers the saga flow)

## Tests
- Unit test: CreateNominationCommandHandler happy path
- Unit test: validator rejects nomination with zero volume
- Integration test: migrations apply, DbContext roundtrip

## K8S & Tilt
- Add deployment, service manifest
- Add migration job manifest
- Add Tilt resources
- Add nginx ingress route

Commit with message: "feat: implement Nominations service"

P11: NominationFulfillment Saga (First Step)

Estimated time: 30–45 minutes Visual checkpoint: Tilt logs show saga started, ScheduleMovement command sent (will fail since Movements doesn't exist — that's expected and demo-perfect)

Implement the NominationFulfillmentSaga in the Nominations service Application layer.
This saga coordinates the end-to-end flow from nomination through accounting.

For now, implement only the first two saga steps. The remaining steps will be wired
during the live demo as services are added.

## Saga: NominationFulfillmentSaga

### Saga Data
- NominationId: Guid
- ContractId: Guid
- ProductCode: string
- NominatedVolume: decimal
- UnitOfMeasure: string
- MovementId: Guid (set when movement is scheduled)
- ActualVolume: decimal (set when movement is confirmed)
- Status: string

### Step 1: Started by NominationSubmitted event
- Set saga data from event properties
- Set Status = "AwaitingMovement"
- Send ScheduleMovement command to "MidstreamHub.Movements" endpoint
- Log: "Saga started for Nomination {NominationId}, scheduling movement"

### Step 2: Handle MovementScheduled event
- Store the MovementId
- Set Status = "MovementScheduled"
- Log: "Movement {MovementId} scheduled for Nomination {NominationId}"

### Step 3 (STUB — will be implemented during demo): Handle MovementConfirmed event
- Placeholder handler that logs "MovementConfirmed handler not yet implemented"

### Step 4 (STUB): Handle VolumesReconciled event
- Placeholder

### Step 5 (STUB): Handle VarianceThresholdExceeded / BalanceAdjustmentCompleted
- Placeholder

## Configuration

The Nominations API Program.cs NServiceBus endpoint configuration must:
- Route ScheduleMovement commands to "MidstreamHub.Movements"
- Route ReconcileVolumes commands to "MidstreamHub.Accounting"
- Route InitiateBalanceAdjustment commands to "MidstreamHub.Accounting"

## Tests

- Saga unit test using NServiceBus.Testing:
  - When NominationSubmitted is received, saga sends ScheduleMovement command
  - When MovementScheduled is received, saga updates MovementId
  - Verify saga correlation by NominationId

## Important

The ScheduleMovement command WILL FAIL at runtime because the Movements service
doesn't exist yet. This is intentional — during the live demo, we will build the
Movements service and show the saga complete successfully. The error in Tilt logs
is part of the demo narrative.

Commit with message: "feat: implement NominationFulfillmentSaga (first step)"

P12: React Frontend

Estimated time: 60–90 minutes Visual checkpoint: Browser at localhost:3000 shows dashboard, contract list, nomination form

Create the MidstreamHub React frontend using Vite + React + TypeScript.
This is the visual centerpiece of the demo. Use the frontend-design plugin/skill
for UI decisions.

## Setup

1. Scaffold with: npm create vite@latest midstreamhub-ui -- --template react-ts
   in src/Web/
2. Install dependencies: react-router-dom, axios (or fetch wrappers), and whatever
   UI component library the frontend-design skill recommends
3. Configure vite.config.ts with API proxy:
   - Target: `process.env.API_PROXY_TARGET || 'http://localhost:8080'` (env var for K8S, localhost fallback for local dev)
   - Include `changeOrigin: true` and `autoRewrite: true` (rewrites redirect Location headers to prevent internal hostname leaks)
   - IMPORTANT: Do NOT use `VITE_` prefix for the env var — Vite exposes `VITE_`-prefixed vars to client code

## Pages

### Dashboard (/)
- Title: "MidstreamHub — NGL Operations"
- Pipeline visualization showing the flow: Contracts → Nominations → Movements → Accounting
- Each stage shows a count badge (number of records in that stage)
- Use a horizontal stepper or pipeline graphic
- Recent activity feed showing last 10 events across all services
- The dashboard should look professional and industry-appropriate

### Contracts (/contracts)
- Table listing all contracts with columns: Contract #, Counterparty, Product, Type,
  Status, Effective Date, Max Volume
- Status badges (color-coded: Draft=gray, Active=green, Suspended=yellow, Expired=red)
- "New Contract" button opening a form:
  - Counterparty dropdown (loaded from API)
  - Product dropdown (loaded from API)
  - Contract type dropdown (must match backend enum: Supply, Transport, Exchange)
  - Date pickers for effective/expiration
  - Max volume input with UOM display
- Contract detail view showing all fields

### Nominations (/nominations)
- Table listing nominations: Nom #, Contract, Product, Volume, Status, Scheduled Date
- Status badges (Submitted=blue, Scheduled=purple, InProgress=orange, Completed=green)
- "New Nomination" button opening a form:
  - Contract dropdown (only active contracts)
  - Product auto-fills from selected contract
  - Volume input with validation against contract max
  - Scheduled delivery date picker
- On submit, shows a toast/notification: "Nomination submitted — saga initiated"

### Movements (/movements) — PLACEHOLDER
- Show "Coming Soon" with a pipeline icon
- This page will be built during the live demo

### Accounting (/accounting) — PLACEHOLDER
- Show "Coming Soon" with a ledger icon

## Layout

- Sidebar navigation with icons for each section
- Header with "MidstreamHub" branding
- Dark/professional color scheme appropriate for enterprise energy software
- Responsive layout (though demo will be on desktop)

## API Client

Create a typed API client module at src/api/:
- contractsApi.ts: getContracts, getContract, createContract, activateContract,
  getProducts, getCounterparties
- nominationsApi.ts: getNominations, getNomination, createNomination
- movementsApi.ts: (placeholder)
- accountingApi.ts: (placeholder)

All API calls go through the configured proxy to nginx ingress.

## TypeScript Types

Define types matching the backend DTOs in src/types/:
- Contract, Counterparty, Product, Nomination, etc.

## Dockerfile.web

- Base: node:22-alpine (both build and dev stages)
- Must trust corporate proxy cert before `npm ci` (Alpine manual cert approach — see CLAUDE.md)
- Dev mode: `npm run dev -- --host 0.0.0.0 --port 3000`
- The existing Tiltfile should pick this up

## K8S web-ui.yaml

- Must include `env` with `API_PROXY_TARGET=http://nginx-ingress:8080` so the Vite dev server inside K8S proxies API requests to the nginx reverse proxy via cluster DNS

## Tests

- At minimum, verify the app compiles: `npm run build`

Commit with message: "feat: add React frontend with dashboard, contracts, and nominations UI"

P13: GitHub Actions CI

Estimated time: 20–30 minutes Visual checkpoint: Workflow file exists, ready to trigger on push

Create the GitHub Actions CI workflow for MidstreamHub.

## .github/workflows/ci.yml

### Triggers
- Push to: main, develop, feature/*, release/*, hotfix/*, support/*, bugfix/*
- Pull request to: main, develop

### Jobs

#### build-and-test
- Runs on: ubuntu-latest
- Steps:
  1. Checkout
  2. Setup .NET 10
  3. Setup Node.js 22
  4. Run: nuke compile
  5. Run: nuke test
  6. Run: cd src/Web/midstreamhub-ui && npm ci && npm run build

#### publish-packages (only on main and develop branches)
- Depends on: build-and-test
- Steps:
  1. Checkout
  2. Setup .NET 10
  3. Run: nuke pack
  4. Publish NuGet packages to GitHub Packages
     - Source: https://nuget.pkg.github.com/ericburcham/index.json
     - API key: ${{ secrets.GITHUB_TOKEN }}

#### docker-build (only on main, develop, release/*)
- Depends on: build-and-test
- Steps:
  1. Checkout
  2. Run: nuke dockerbuild
  3. (No push for now — just verify images build)

### Environment
- Use the GITHUB_TOKEN for GitHub Packages authentication
- No external secrets required for the basic pipeline

Commit with message: "ci: add GitHub Actions workflow for build, test, and package publishing"

P14: End-to-End Verification & Polish

Estimated time: 30–45 minutes Visual checkpoint: Full system runs in Tilt, React shows data, saga fires

Perform end-to-end verification of everything built so far. Fix any issues.

## Verification Checklist

1. Run `nuke` (default target: clean → restore → compile → test)
   - All unit tests pass
   - Build succeeds with zero warnings

2. Run `tilt up` and verify:
   - SQL Server starts and is healthy
   - RabbitMQ starts, management UI accessible at localhost:15672
   - Contracts migration job runs successfully
   - Nominations migration job runs successfully
   - Contracts API starts, OpenAPI docs at localhost:5001/openapi
   - Nominations API starts, OpenAPI docs at localhost:5002/openapi
   - React UI loads at localhost:3000

3. Test the business flow manually:
   - GET /api/contracts/products → returns 5 NGL products
   - GET /api/contracts/counterparties → returns 4 counterparties
   - GET /api/contracts → returns the seeded contract
   - POST /api/nominations with:
     {
       "contractId": "<seeded contract ID>",
       "nominatedVolume": 1000,
       "unitOfMeasure": "bbl",
       "scheduledDeliveryDate": "2025-06-15"
     }
   - Verify NominationSubmitted event published (check RabbitMQ management)
   - Verify saga starts (check Nominations API logs in Tilt)
   - Verify ScheduleMovement command fails (Movements endpoint not found — EXPECTED)

4. Verify React UI:
   - Dashboard shows contract and nomination counts
   - Contracts page lists the seeded contract
   - Nomination form submits successfully
   - Nominations page shows the new nomination

5. Fix any issues found. Run `nuke` again to confirm clean build.

6. Push to GitHub:
   - git remote add origin https://github.com/ericburcham/claude-code-demo.git
   - git push -u origin main
   - Create develop branch, push it
   - Verify GitHub Actions workflow triggers

7. Final commit with message: "chore: end-to-end verification and polish"

Demo Script (1 Hour)

Pre-Demo Setup (do this 15 minutes before the CFO arrives)

  1. Run tilt up and verify all resources are green
  2. Open in browser tabs:
    • Tab 1: React UI at localhost:3000
    • Tab 2: Tilt dashboard (localhost:10350)
    • Tab 3: RabbitMQ management (localhost:15672)
    • Tab 4: Terminal with Claude Code ready
  3. Create a nomination via the UI to trigger the saga (it will show the error)
  4. Have this script open on a second monitor or printed

Segment 1: Context Setting (0:00–0:10)

[No Claude Code — just talking and showing]

Talking points:

  • "Let me show you what one developer built in about 8 hours using Claude Code."
  • Walk through the React dashboard — show contracts, products, nominations
  • Show the Tilt dashboard — point out running services, K8S pods
  • Show the RabbitMQ management UI — explain message-based architecture
  • "This is production-grade architecture: CLEAN architecture, DDD, CQRS, NServiceBus sagas, central package management, StyleCop enforcement."
  • Show the saga error in the logs: "This nomination triggered a saga that tried to schedule a movement — but the Movements service doesn't exist yet. Let's build it live."

Key message: "8 hours of work produced this entire foundation. Now watch what happens in the next 50 minutes."


Segment 2: Build the Movements Service (0:10–0:25)

[Claude Code — Subagents]

Narrate before executing: "I'm going to ask Claude Code to build the entire Movements service. It will use a subagent — essentially delegating the scaffolding work to a specialized agent that knows our architecture patterns."

Prompt D01:

Use the service-scaffolder agent to create the Movements service for MidstreamHub.

## Service Definition

- Service name: Movements
- Schema: movements
- API port: 5003
- NServiceBus endpoint: MidstreamHub.Movements

## Domain Model

### Aggregate Root: Movement
- Id: Guid
- MovementNumber: string (auto-generated, "MVT-2025-001")
- NominationId: Guid (correlation to Nominations SOR)
- ContractReference: ContractReference value object (hydrate from Contracts API)
- ProductCode: ProductCode value object
- ScheduledVolume: Volume value object
- ActualVolume: Volume value object (nullable, set on confirmation)
- ScheduledDate: DateTime
- ConfirmedDate: DateTime? (nullable)
- MeasurementGravity: decimal? (API gravity at measurement point)
- MeasurementTemperature: decimal? (degrees F at measurement)
- ReceiptPoint: string (location identifier)
- DeliveryPoint: string (location identifier)
- Status: enum (Scheduled, InTransit, Delivered, Confirmed, Rejected)
- Methods: Schedule(), BeginTransit(), Deliver(), Confirm(decimal actualVolume, decimal gravity, decimal temp), Reject(string reason)

## NServiceBus Handlers

### HandleScheduleMovement (command handler)
- Receives ScheduleMovement command from the saga
- Creates a new Movement aggregate in Scheduled status
- Hydrates ContractReference from Contracts API
- Publishes MovementScheduled event
- Log: "Movement {MovementNumber} scheduled for Nomination {NominationId}"

## Application Layer

### Commands
- ConfirmMovementCommand + Handler + Validator
  - Sets actual volume, measurement data
  - Publishes MovementConfirmed event via NServiceBus
  - Updates status to Confirmed

### Queries
- GetMovementByIdQuery + Handler
- GetMovementListQuery + Handler (filterable by status, nomination)

## Api Endpoints (/api/movements/)
- GET / — list movements
- GET /{id} — get movement by ID
- PUT /{id}/confirm — confirm movement with measurement data

Follow the exact patterns from the Contracts service. Include migrations, migration
runner, K8S manifests, Tilt resources, and tests.

While Claude Code works, narrate:

  • "Notice it's reading the CLAUDE.md for project standards."
  • "It's consulting the clean-architecture and ddd-patterns skills."
  • "The CLAUDE.md and skills ensure code follows our architecture standards."
  • Point out the subagent delegation in the Claude Code output.

After completion, show:

  • Tilt dashboard with new Movements service appearing
  • Tilt logs showing the migration job running
  • "Let's see if the saga completes now..." (it won't yet — we need the React page)

Segment 3: Movement Tracking UI + Saga Wiring (0:25–0:40)

[Claude Code — Frontend feature agent + skills]

Narrate: "Now let's add the visual layer. I'll use the frontend-feature agent and the frontend-design skill to build the Movements tracking page."

Prompt D02:

Use the frontend-feature agent to build the Movements page in the React UI.

## Requirements

### Movements Page (/movements)
- Replace the "Coming Soon" placeholder
- Table of movements: Movement #, Nomination, Product, Scheduled Vol, Actual Vol,
  Status, Scheduled Date, Confirmed Date
- Status badges: Scheduled=blue, InTransit=purple, Delivered=orange, Confirmed=green, Rejected=red
- Row click opens a detail panel/drawer showing full movement info including
  measurement data (gravity, temperature, receipt/delivery points)
- "Confirm Movement" action button on Delivered-status movements:
  - Opens a form for: Actual Volume, Gravity (°API), Temperature (°F)
  - Submits to PUT /api/movements/{id}/confirm
  - Shows success toast: "Movement confirmed — reconciliation initiated"
- Apply the frontend-design skill for professional styling

### Dashboard Update
- Update the dashboard pipeline visualization to show real Movement counts
- The Movements stage should no longer show as placeholder
- Add movement events to the activity feed

### API Client
- Implement movementsApi.ts with: getMovements, getMovement, confirmMovement
- Add Movement TypeScript types

## Testing

Now let's verify the full saga flow:
1. Create a nomination via the UI
2. Watch Tilt logs — the saga should send ScheduleMovement
3. The Movements service should create a Movement and publish MovementScheduled
4. The saga should log "Movement scheduled"
5. Show the new movement on the Movements page

While Claude Code works, narrate:

  • "The frontend-design skill ensures this doesn't look like a generic Bootstrap template."
  • "Notice how it follows the patterns from our skills and CLAUDE.md."

After completion, LIVE DEMO:

  1. Open the React UI — show the new Movements page
  2. Go to Nominations, submit a new nomination
  3. Switch to Tilt dashboard — watch the saga logs in real time
  4. Switch back to Movements page — show the new movement appearing
  5. "The saga is working. Nomination triggered a movement. Now let's add accounting."

This is the money shot. The CFO sees a real business transaction flow across services in real time, visualized in the UI.


Segment 4: Accounting Service via Agent Teams (0:40–0:50)

[Claude Code — Agent Teams]

Narrate: "For the final service, I'll use Claude Code's agent teams feature. This lets multiple agents work on different aspects simultaneously — one on the backend service, one on the frontend, one on the saga integration."

Prompt D03:

Create an agent team with three teammates — "accounting-backend", "saga-wiring",
and "accounting-ui" — to build the Accounting service and wire it into the full saga.
The three workstreams are fully independent and should proceed in parallel.
Teammates "accounting-backend" and "saga-wiring" should coordinate on shared message
contracts (ReconcileVolumes, VolumesReconciled, VarianceThresholdExceeded,
BalanceAdjustmentCompleted) before implementation begins. Wait for teammates to
complete their workstreams before synthesizing results.

Coordinate three parallel workstreams:

## Workstream 1: Accounting Service Backend (teammate: accounting-backend)

Create the Accounting service following established patterns.

### Domain: BalancePeriod aggregate
- Id: Guid
- PeriodStart: DateTime, PeriodEnd: DateTime
- NominationId: Guid, MovementId: Guid
- PlannedVolume: Volume, ActualVolume: Volume
- Variance: Volume (calculated: Actual - Planned)
- VariancePercent: decimal
- Status: enum (Pending, Reconciled, AdjustmentRequired, Adjusted)
- AdjustmentVolume: Volume? (nullable)
- AdjustmentReason: string? (nullable)
- Methods: Reconcile(), RequiresAdjustment(decimal thresholdPercent), ApplyAdjustment(Volume, string reason)

### NServiceBus Handler: HandleReconcileVolumes
- Creates BalancePeriod, calculates variance
- If variance <= 2% → publish VolumesReconciled
- If variance > 2% → publish VarianceThresholdExceeded
- Log: "Reconciliation for Movement {MovementId}: variance {VariancePercent}%"

### NServiceBus Handler: HandleInitiateBalanceAdjustment
- Applies adjustment to BalancePeriod
- Publishes BalanceAdjustmentCompleted
- Log: "Balance adjustment applied: {AdjustmentVolume} bbl, reason: {Reason}"

### API Endpoints (/api/accounting/)
- GET /balance-periods — list all balance periods
- GET /balance-periods/{id} — detail view

Include migrations, K8S manifests, Tilt resources.

## Workstream 2: Complete the Saga (teammate: saga-wiring)

In the Nominations service, update the NominationFulfillmentSaga:
- Step 3: Handle MovementConfirmed → send ReconcileVolumes command to Accounting
- Step 4: Handle VolumesReconciled → MarkAsComplete, log success
- Step 5: Handle VarianceThresholdExceeded → send InitiateBalanceAdjustment
- Step 6: Handle BalanceAdjustmentCompleted → MarkAsComplete, log completion

Update saga tests to cover all paths.

## Workstream 3: Accounting UI + Dashboard (teammate: accounting-ui)

### Accounting Page (/accounting)
- Replace placeholder with balance period table
- Columns: Period, Nomination, Planned Vol, Actual Vol, Variance, Variance %, Status
- Color-code variance: green (<1%), yellow (1-2%), red (>2%)
- Detail view showing adjustment info if applicable

### Dashboard Final Update
- All four pipeline stages show real counts
- Activity feed shows reconciliation events
- Add a summary card: "Total Volume Moved" and "Average Variance %"

While agents work, narrate:

  • "Three agents are working simultaneously — backend, saga, and frontend."
  • "This is where agent teams shine. Each has focused context."
  • Point to the Claude Code output showing parallel agent coordination.

Segment 5: Full Pipeline Demo + CI (0:50–0:58)

[Live demonstration — no new prompts]

Walk through the complete flow:

  1. Dashboard shows all 4 services operational
  2. Create a new nomination in the UI
  3. Watch Tilt logs as the saga executes end-to-end:
    • NominationSubmitted → ScheduleMovement → MovementScheduled
  4. Confirm the movement via the UI (set actual volume slightly different from planned)
  5. Watch: MovementConfirmed → ReconcileVolumes → VolumesReconciled or VarianceThresholdExceeded
  6. Show the Accounting page with the reconciliation result
  7. If variance > 2%, show the compensating transaction in action

Then push to GitHub:

Prompt D05:

Create a feature branch, commit all changes, and push to trigger CI.

git checkout -b feature/add-movements-and-accounting
git add -A
git commit -m "feat: add Movements and Accounting services with full saga flow"
git push -u origin feature/add-movements-and-accounting

Show GitHub Actions triggering. "The Nuke build runs in CI — same targets we use locally. Tests, package publishing, Docker builds."


Segment 6: Wrap Up (0:58–1:00)

Talking points:

  • "In one hour, you watched Claude Code build two production-grade microservices, wire them into an NServiceBus saga with compensating transactions, and add a React frontend — all following our architectural standards."
  • "The code passes StyleCop analysis, has unit and integration tests, uses proper DDD patterns, and deploys on Kubernetes."
  • "This is what I mean by compressing weeks into hours. Not replacing engineers — amplifying them. Every prompt I wrote required understanding the architecture. Claude Code needs oversight, but it's a force multiplier."
  • "And the CI pipeline just confirmed everything compiles and tests pass."

Appendix: Risk Mitigation

If something fails during the live demo:

Failure Recovery
Tilt resource won't start Show the error, explain it, fix it live — this demonstrates the "needs oversight" message
Saga doesn't complete Check RabbitMQ management for queued messages, explain the debugging process
React page has a bug Fix it with a quick Claude Code prompt — fast iteration is part of the story
Claude Code generates bad code This IS the demo. "This is why you need senior engineers. Watch me fix it."
CI pipeline fails Show the failure, explain it. "Same as any team — CI catches issues."

Timing buffer:

If running ahead of schedule: show the RabbitMQ management UI in detail, explore the OpenAPI docs, or ask Claude Code to add a feature live (e.g., "add a volume unit conversion utility to Domain.Common").

If running behind: skip the CI push (D05) and just show the full pipeline demo. The CI workflow existing is enough — you can show it triggers later.


Appendix: NuGet Package Versions Reference

Use these minimum versions in Directory.Packages.props (verify latest stable at prep time):

Package Minimum Version
Autofac.Extensions.DependencyInjection 10.0.0
MediatR.Extensions.Autofac.DependencyInjection 12.1.0
FluentMigrator 6.2.0
FluentValidation 11.11.0
MediatR 12.4.1
Microsoft.EntityFrameworkCore.SqlServer 10.0.0-preview.2.25163.8
Microsoft.Data.SqlClient 6.0.1
NServiceBus 9.2.5
NServiceBus.Extensions.Hosting 3.0.1
NServiceBus.RabbitMQ 9.2.2
NServiceBus.Persistence.Sql 8.1.1
NServiceBus.Testing 9.0.1
NUnit 4.3.2
Testcontainers 4.3.0
StyleCop.Analyzers 1.2.0-beta.556
Swashbuckle.AspNetCore 10.1.5
Microsoft.AspNetCore.OpenApi 10.0.4
Nuke.Common 9.0.4