v1.0.0-alpha pre-releases live on NuGet.org · dotnet tool install -g vouchfx --prerelease · v1.0 GA in preparation

End-to-end testing for
distributed systems,
authored in YAML.

vouchfx compiles declarative .e2e.yaml specs into memory-safe C#, orchestrates the full container topology with .NET Aspire + Testcontainers, and proves one business transaction works as it crosses a REST call, a Kafka event, a database mutation and an outbound webhook — the seams where distributed systems actually break.

It is not a unit-test framework and not a UI/browser tool.

The problem

Unit tests pass. Production still breaks at the seams.

A modern transaction is not a function call — it is a relay across services, brokers, databases and webhooks. Each component is tested in isolation and works. The handoffs between them are where ordering, timing, schema drift and partial failure hide. vouchfx tests that relay as a single, declarative scenario.

Cross-boundary by design

One scenario spans REST, Kafka, Postgres and outbound webhooks — exercising the contracts between systems, not within them.

Determinism by default

No hand-rolled Thread.Sleep. Asynchronous assertions are a first-class RETRY mode the engine owns, with bounded exponential backoff.

Topological parity

The same suite runs unchanged on a laptop, in SaaS, or in CI — because the compiled delegate depends only on a typed contract that orchestration produces.

How it works

Compile once. Isolate. Run N times. Unload to baseline.

A spec is parsed to an AST, lowered to Turing-complete C# script (CSX), and compiled exactly once through Roslyn into a collectible assembly. Orchestration stands up the topology; the delegate runs against the discovered endpoints; the context unloads and resets — no leaked assemblies, no drift between runs.

01

Author & validate

Write .e2e.yaml; validate against a single JSON Schema before any container starts.

02

Compile (once)

YAML → AST → CSX → CSharpScript.Create<T>().Emit() into a collectible AssemblyLoadContext.

03

Orchestrate

Headless .NET Aspire AppHost + Testcontainers bring up services and managed dependencies, health-gated.

04

Execute

Invoke the delegate against discovered endpoints; Polly v8 drives RETRY polling under the hood.

05

Verdict & unload

Emit a verdict to a JSON Lines event stream, .Unload() the context, and reset to baseline.

Architecture

Five layers, each exposing one narrow typed contract upward.

The contract that makes a suite portable: the compiled delegate touches the environment only through a typed ScriptGlobalVariables, and orchestration is its sole producer. Nothing else bridges the boundary.

1

Authoring

The .e2e.yaml grammar, JSON Schema, VSCode/LSP tooling.

2

Compilation

Parser → AST → CSX → Roslyn delegate, compiled once.

3

Orchestration

Aspire AppHost, Testcontainers, health-gated startup.

4

Execution

Delegate host, collectible AssemblyLoadContext, Polly v8 resilience.

5

Infrastructure fabric

Local Docker, SaaS, or on-prem Kubernetes.

contract compiled delegate → ScriptGlobalVariables ← orchestration

A test, in shape

Four sections. Only steps is mandatory.

State threads forward between steps through capture and {placeholder} substitution. One generated identifier from step one can appear in the assertions of step five — across heterogeneous systems.

provision-customer.e2e.yaml
metadata:
  name: New customer is provisioned end-to-end
  owner: payments-team
  tags: [checkout, billing]

environment:
  services:                          # the system under test — any container, any stack
    orders-api: { image: ghcr.io/acme/orders-api:latest }
  dependencies:                      # managed Aspire resources
    orders-db: { type: postgres, version: "16" }
    events:    { type: kafka, schemaRegistry: true }

variables:
  basePath: /api/v1

steps:
  - id: create-user
    type: http.rest
    target: orders-api
    method: POST
    path: "{basePath}/users"
    body: { email: ada@example.com }
    capture:
      newUserId: $.id            # JSONPath → shared context

  - id: expect-billing-event
    type: mq-expect.kafka
    topic: billing.accounts
    verifyMode: RETRY            # engine polls with backoff — never Thread.Sleep
    timeout: 30s
    match:
      key: "{newUserId}"

  - id: assert-projection
    type: db-assert.postgres
    target: orders-db
    verifyMode: RETRY
    query: SELECT 1 FROM accounts WHERE user_id = '{newUserId}'
    expect: { rows: 1 }

Secrets are references, never literals — ${secret:vault/orders-db/conn}, resolved at execution time and redacted in every report. Test doubles (WireMock/Mountebank) are ordinary containers in environment, not a bolted-on mocking feature.

Real-world samples

Clone, run one command, see a complete end-to-end suite.

The vouchfx-samples site documents four production-grade sample applications (in four stacks) with complete test suites demonstrating patterns across multiple providers and technologies.

Orders · C# + ASP.NET

Stack: ASP.NET Core, PostgreSQL, Kafka, Webhook.

Demonstrates: REST, database assertions, message publishing/consumption, webhook listening, RETRY polling.

View →

Inventory · Python + FastAPI

Stack: FastAPI, MySQL, RabbitMQ, Redis.

Demonstrates: HTTP, MySQL assertions, RabbitMQ publish/expect, Redis cache assertions, multi-step state threading.

View →

Payments · Java + Spring Boot

Stack: Spring Boot, SQL Server, NATS JetStream, Mailpit.

Demonstrates: REST, SQL Server assertions, NATS publish/expect with JetStream, email capture, connection-string injection.

View →

Ledger · Node.js + JSON-RPC

Stack: Node.js (node:http), PostgreSQL, Kafka, Community provider rpc.json-rpc.

Demonstrates: Custom provider consumption, JSON-RPC protocol, PostgreSQL mutations, message publishing, custom runner pattern.

View →

The central problem

Dynamic compilation that doesn't leak.

Naïvely evaluating a script per run leaks an uncollectable assembly every time. vouchfx is built around the opposite discipline — and ships a CI memory-leak regression test over the full transitive closure of every Core provider as a permanent gate.

Never
// leaks an uncollectable assembly per call
await CSharpScript.EvaluateAsync(code);
Always
// compile once …
var script = CSharpScript.Create<T>(code);
script.GetCompilation().Emit(stream);
// … isolate, invoke N times, then collect
var alc = new AssemblyLoadContext(isCollectible: true);
// … run …
alc.Unload();
5,000load–unload cycles
~1.3 KBnet heap delta (2 MB threshold)
20 / 20health-gated topology starts

The two defining risks empirically retired, guarded permanently in CI.

Provider model

Step type = <family>.<provider> — intent, then technology.

Providers are compile-time, source-level plugins: add a project, implement the frozen v1.x contract, mark it [StepProvider], and a reflective registry discovers it at startup. No runtime loader, no dynamic assembly loading, no sandbox. Models are strongly-typed records, never Dictionary<string,object>.

http: rest, soap db-assert: postgres, mysql, sqlserver, mongodb, dynamodb mq-publish: kafka, rabbitmq, nats, asb, redis mq-expect: kafka, rabbitmq, nats, asb, redis cache-assert: redis, elasticsearch metrics-assert.prometheus storage-assert.s3 trace-expect.otlp mail-expect.smtp webhook-listen.http script.csharp

Two governance tiers, plus the Vouched badge

Core · Community — plus the maintainer-awarded Vouched badge, all Apache-2.0. Explore the community provider hub.

Frozen v1 contract

IStepProvider / IStepBinder<T> / IStepValidator<T> / IStepCompiler<T> / IResourceContributor<T> — stable for the v1.x engine series.

Any stack under test

vouchfx is a .NET-native engine, but it talks to the system under test over the wire. Aspire is polyglot — your services can be Java, Go, Python, Node or .NET.

Verdict taxonomy

Four outcomes, kept distinct everywhere.

Taxonomy, reporting and exit codes never conflate them. Only Fail breaks CI by default — because conflating an environment error with a real defect is how teams stop trusting a tool.

Pass

Every assertion held.

Fail

A defect in the system under test. The only outcome that breaks CI by default.

Environment error

Infrastructure: unhealthy container, pull / tunnel / seed failure.

Inconclusive

Timeout, partition outlasted grace, or an upstream capture unmet.

One schema-versioned JSON Lines event stream feeds every renderer — terminal, HTML, JUnit XML, dashboard — and the Healer agent. Each step-attempt is recorded individually, so the RETRY timeline renders without re-running.

Status & roadmap

Risk-ordered delivery — the hard problems first.

Work is sequenced by risk, not visibility: the memory model and orchestration were de-risked first, and the memory-leak regression test has been a permanent CI gate from the start. The full picture — including what stays free permanently — is on the public roadmap.

  1. Delivered

    Foundations proven

    Memory-safe dynamic compilation and health-gated Aspire orchestration empirically validated. Buildable .NET 8 solution; frozen provider contract; JSON Lines event envelope.

  2. Delivered

    Core compiler end-to-end

    The YAML→AST→CSX compiler runs real scenarios end-to-end with declarative seeding, secret references, capture, the reflective provider registry and the terminal renderer.

  3. Delivered

    Full step set & SDK

    All twenty-five Core providers across eleven families plus engine-owned RETRY; the v1 schema, provider contract and event-wire contract frozen; the Provider SDK published on NuGet.org with worked examples; runner selection and the polling timeline.

  4. Delivered

    Tooling & hardening

    VSCode extension, CLI, HTML/JUnit reporting, CI templates for GitHub and GitLab, the documentation set, the accessibility review, and a signed, provenance-attested release pipeline — with the four-technology reference scenario green.

  5. In progress

    Toward v1.0 GA

    The first public releases have shipped: the v1.0.0-alpha series is live on NuGet.org and GitHub Releases (currently v1.0.0-alpha.9), signed and provenance-attested. Remaining for GA: real-world validation in the open — through the samples, the migration guide and the community provider hub — and stabilisation of the Provider SDK at 1.0.0 final.

Read the design. It's the source of truth.

vouchfx is a fully-specified system: an architecture blueprint, a DSL specification, and a public roadmap — all in the open.