Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
fbd85fb
chore: add Claude Code project instructions (CLAUDE.md)
Mar 10, 2026
318dd5f
docs: add demo prompt set and script for MidstreamHub
Mar 10, 2026
71348d2
chore: initialize solution structure and central package management
Mar 10, 2026
5f42109
chore: add Claude Code configuration, skills, hooks, and agents
Mar 10, 2026
8b35649
chore: add Autofac DI and K8S manifests skills
Mar 10, 2026
0db16db
build: add Nuke build system with full target chain
Mar 10, 2026
d1f0841
feat: add shared NuGet packages (Contracts.Messages, Domain.Common, I…
Mar 10, 2026
ce04306
chore: add .vscode/ to .gitignore
Mar 10, 2026
fe9cb6f
infra: add K8S manifests for SQL Server, RabbitMQ, and nginx ingress
Mar 10, 2026
0d84be0
infra: add Tiltfile with Dockerfiles for local K8S development
Mar 10, 2026
68d27e3
docs: add Contracts service reference implementation design spec
Mar 10, 2026
25e21ec
docs: add Contracts service implementation plan with review fixes
Mar 10, 2026
d0d29e3
chore: add missing NuGet packages and EF Core parameterless construct…
Mar 10, 2026
ba868b7
feat(contracts): add domain layer — Contract aggregate, Counterparty,…
Mar 10, 2026
4aee8db
feat(contracts): add application layer — commands, queries, DTOs, val…
Mar 10, 2026
3cc3f05
feat(contracts): add infrastructure layer — EF Core context, configur…
Mar 10, 2026
cd79119
feat(contracts): add API layer — Program.cs, endpoints, Autofac root …
Mar 10, 2026
d2f3bf3
feat: implement Contracts service (reference implementation)
Mar 10, 2026
0a95fd0
fix: remove unnecessary Infrastructure→Application reference and add …
Mar 10, 2026
7865f59
chore: remove failing Claude Code hooks
Mar 10, 2026
75d4769
remove unneeded .gitkeep files
Mar 10, 2026
4dcdd0d
feat: add Contracts schema migrations with Fluent Migrator
Mar 10, 2026
c47cc1f
feat: add seed data for NGL products, counterparties, and sample cont…
Mar 10, 2026
b7d4cf8
fix: resolve Docker build and Tilt deployment issues
Mar 10, 2026
0904825
fix: fix RabbitMQ connection and add readiness probe
Mar 10, 2026
b724cb9
feat: implement Nominations service
Mar 10, 2026
fbec4a3
fix: isolate FluentMigrator VersionInfo tables per service schema
Mar 11, 2026
45d3ae1
feat: implement NominationFulfillmentSaga (first step)
Mar 11, 2026
41fe097
feat: add React frontend with dashboard, contracts, and nominations UI
Mar 11, 2026
ab9b636
fix: replace K8S Ingress resource with nginx reverse proxy API gateway
Mar 11, 2026
1aa699e
fix: resolve API connectivity and frontend proxy issues
Mar 11, 2026
d60c629
fix: resolve frontend proxy issues, contract type mismatch, and add c…
Mar 11, 2026
e756389
Merge branch 'feature/react-frontend' into develop
Mar 11, 2026
1d6d676
feat: add real-time polling to dashboard for automatic data refresh
Mar 11, 2026
722b67c
update contract seed and example values
Mar 11, 2026
58a323a
feat: add GitHub Actions CI workflow for build, test, and publish
Mar 11, 2026
37e9568
fix: update GitHub Actions to Node.js 24-compatible versions
Mar 11, 2026
6ad3304
Merge pull request #1 from ericburcham/feature/ci-build
ericburcham Mar 11, 2026
0d4e74c
fix: add corporate proxy SSL cert to web UI Docker build
Mar 11, 2026
24db890
fix: upgrade Swashbuckle to v10.1.5 for .NET 10 compatibility
Mar 11, 2026
5aacdf7
refactor: move OpenAPI docs from /swagger to /openapi
Mar 11, 2026
23b53f4
docs: add Alpine SSL cert and OpenAPI conventions to CLAUDE.md
Mar 11, 2026
702186a
docs: update script.md to match current repository state
Mar 11, 2026
a9dc03f
fix: use DockerTasks for Docker builds to fix CI stderr logging
Mar 11, 2026
e45a27b
Merge pull request #2 from ericburcham/feature/end-to-end-testing
ericburcham Mar 11, 2026
d155b05
fix script file
Mar 11, 2026
a3d99ef
feat: scaffold Movements service with full CLEAN architecture
Mar 11, 2026
bc233e9
add common stuff to our solution file
Mar 11, 2026
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
135 changes: 135 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# 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
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`
19 changes: 19 additions & 0 deletions .claude/agents/frontend-feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 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.
26 changes: 26 additions & 0 deletions .claude/agents/service-scaffolder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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`
14 changes: 14 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"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
}
}
34 changes: 34 additions & 0 deletions .claude/skills/clean-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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
27 changes: 27 additions & 0 deletions .claude/skills/cqrs-mediatr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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`
28 changes: 28 additions & 0 deletions .claude/skills/ddd-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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
76 changes: 76 additions & 0 deletions .claude/skills/dependency-injection-autofac.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Dependency Injection with Autofac Skill

## Module-per-Layer Pattern

Each CLEAN layer in a service has its own Autofac module:

- `{Service}DomainModule` — Domain layer registrations (if any)
- `{Service}ApplicationModule` — MediatR handlers, FluentValidation validators
- `{Service}InfrastructureModule` — DbContext, repositories, HTTP clients

The Api layer's root module composes all layer modules:

```csharp
public class {Service}Module : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterModule<{Service}DomainModule>();
builder.RegisterModule<{Service}ApplicationModule>();
builder.RegisterModule<{Service}InfrastructureModule>();
}
}
```

## MediatR Registration

Register MediatR handlers via assembly scanning in the Application module:

```csharp
builder.RegisterMediatR(typeof({Service}ApplicationModule).Assembly);
```

## FluentValidation Registration

Register validators via `AsClosedTypesOf` in the Application module:

```csharp
builder.RegisterAssemblyTypes(ThisAssembly)
.AsClosedTypesOf(typeof(IValidator<>))
.AsImplementedInterfaces();
```

## Repository Registration

Register repositories in the Infrastructure module:

```csharp
builder.RegisterType<{Entity}Repository>()
.As<I{Entity}Repository>()
.InstancePerLifetimeScope();
```

## DbContext Registration

Register EF Core DbContext in the Infrastructure module:

```csharp
builder.Register(c =>
{
var optionsBuilder = new DbContextOptionsBuilder<{Service}DbContext>();
optionsBuilder.UseSqlServer(connectionString);
return new {Service}DbContext(optionsBuilder.Options);
}).AsSelf().InstancePerLifetimeScope();
```

## Program.cs Host Integration

Configure Autofac as the DI container in the Api layer:

```csharp
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(container =>
{
container.RegisterModule<{Service}Module>();
});
```
Loading