⚠️ Under Construction: This library is actively being developed and is not intended for production use yet. Monorepo for the ForgeTrust.AppSurface projects
ForgeTrust.AppSurface is a collection of .NET libraries designed to provide a lightweight, modular startup pipeline for both console and web applications.
If you are deciding which package to install first, start with the AppSurface v0.1 package chooser.
The primary vision of AppSurface is to simplify application bootstrapping by encouraging composition through small, focused modules. Instead of monolithic startup classes or scattered configuration logic, AppSurface allows developers to encapsulate features into reusable modules that handle:
- Dependency Injection (DI) registration
- Host configuration
- Application-specific startup logic
This approach aims to:
- Share cross-cutting concerns between different application types (e.g., sharing logging or database setup between a Web API and a background Console worker).
- Keep applications minimal, with infrastructure heavily decoupled from business logic.
- Provide consistency in how applications are initialized and configured, regardless of whether they are web or console apps.
- Modularity: Everything should be a module that does one thing well. Take what you need and don't get burdened by what you don't.
- Consistency: A unified
AppSurfaceStartuppipeline for different project types. - Flexibility: Open for integration with external libraries (Autofac, OpenApi, etc.) and stick to framework provided abstractions where possible.
- Performance: Designed to have minimal overhead on the application startup and execution.
- Ease of Use: Simple APIs and clear patterns to make getting started frictionless.
- Convention over Configuration: Sensible defaults are provided so only minimal configuration is required.
- Secure By Default: Security best practices are applied automatically where appropriate.
- Use
IMemofor application and service-layer caching (for example, web modules and domain services). - Use direct
IMemoryCacheonly inside caching infrastructure (theForgeTrust.AppSurface.Cachingpackage) or framework integration points whereIMemocannot be injected. - If a module depends on
AppSurfaceCachingModule, do not callAddMemoryCache()again in that module. - Prefer one cache boundary per data snapshot. In AppSurface Docs,
DocAggregatorowns both docs aggregation and search-index payload caching so downstream controllers consume one shared snapshot.
- AppSurface v0.1 package chooser - the generated install map for direct-install packages, support/runtime packages, and proof-host surfaces.
- ForgeTrust.AppSurface.Core – Core abstractions for defining modules, starting an application via
AppSurfaceStartupandStartupContext, and running AppSurface-owned process workflows through a CliWrap-backed policy surface.
- ForgeTrust.AppSurface.Auth – Surface-neutral auth vocabulary for AppSurface modules, including user/session/context contracts, auth outcome results, durable external-subject to app-user-id mapping contracts, passive login/logout prompts, passive audit event descriptions, and no runtime request or identity-provider behavior.
- ForgeTrust.AppSurface.Auth.AspNetCore – ASP.NET Core adapter that maps existing host request auth context and named policies into AppSurface auth results without owning schemes, middleware, challenges, forbids, redirects, or identity-provider setup. Run the Auth Web/RazorWire proof to see one host policy drive both API and rendered UI state.
- ForgeTrust.AppSurface.Intelligence - Product-intelligence event contracts, lifecycle metadata, privacy validation, and host-owned sink hooks for forwarding sanitized AppSurface product events to systems such as PostHog without taking a vendor dependency.
- ForgeTrust.AppSurface.Flow – Typed long-running process contracts, generated-case authoring, graph validation, definition registry, and an in-memory runner for local tests and hello-world flows.
- ForgeTrust.AppSurface.Flow.DurableTask – Durable Task adapter boundary with runner/client services, resume-event authorization, timeout, late-event and retry behavior, and context serialization validation.
- ForgeTrust.AppSurface.Console – Helpers for building command line apps with CliFx, source-generated command descriptors, a
CriticalService-based command runner, and helpers for configuring services.
- ForgeTrust.AppSurface.Web – Bootstraps ASP.NET Core apps, lets modules register pre-routing middleware, endpoint-aware middleware, and endpoints, and includes conventional browser status pages plus opt-in production 500 pages.
- ForgeTrust.AppSurface.Web.OpenApi – Optional module that adds OpenAPI generation with development-only endpoint exposure by default.
- ForgeTrust.RazorWire – Adds reactive Razor-based streaming, islands, and CDN-default export tooling for server-rendered web apps.
- ForgeTrust.AppSurface.Docs – Reusable Razor Class Library package that serves harvested source docs with section-first landing, sidebar, search, built-in trust plus contributor-provenance details, and optional published-version archive surfaces.
- ForgeTrust.AppSurface.Docs.Standalone – Thin export host for exporting or serving AppSurface Docs as an application.
- ForgeTrust.AppSurface.Web.Scalar – Optional module that serves the Scalar API reference UI when both Scalar and OpenAPI exposure gates allow it.
- ForgeTrust.AppSurface.Cli – Public
appsurfacecommand-line tool, includingappsurface docspreview/export workflows,appsurface coverage runprivate test orchestration,appsurface coverage mergeCobertura fan-in, andappsurface coverage gatelocal threshold enforcement.
- ForgeTrust.AppSurface.Dependency.Autofac – Optional integration with the Autofac IoC container so modules can participate in Autofac service registration.
- ForgeTrust.AppSurface.Aspire – Local .NET Aspire AppHost composition with AppSurface modules, CLI-selectable profiles, and reusable Aspire components.
These packages are designed to work together so that features can be shared across different application types while maintaining a consistent startup approach.
If you want to see value first, run the web hello world:
dotnet run --project examples/web-app -- --port 5055Then, from another terminal, prove the running endpoint:
curl http://127.0.0.1:5055Expected response:
Hello World from the root!
That example is the smallest concrete path through ForgeTrust.AppSurface.Web: a root module, one mapped endpoint, and the AppSurface startup pipeline doing the hosting work.
To verify the browser/API error-page contract instead, run the focused proof:
bash examples/web-error-pages/verify.shThat proof starts a local production-mode web app, checks conventional browser 401, 403, 404, and 500 pages, and verifies API requests do not receive surprise browser HTML.
If you are evaluating packages from your own app project rather than running this repo, use the package-first path to create a fresh ASP.NET Core app, install ForgeTrust.AppSurface.Web, and verify the first route. Run the package command from the app project directory, or pass the project path explicitly. The generated package chooser in packages/README.md is the install map for picking optional modules after that first proof.
dotnet package add ForgeTrust.AppSurface.WebAdd optional modules only when the generated chooser points you to them.
If you need a composed product-shaped proof instead of a single package slice, run the product-readiness lab:
dotnet run --project examples/product-readiness-lab/ProductReadinessLab.csproj -- --reportThat fast command emits a no-infrastructure readiness report. To exercise every row the local lab can prove, including Postgres-backed product-state persistence, run the AppHost verifier:
aspire run --non-interactive --apphost examples/product-readiness-lab-apphost/ProductReadinessLabAppHost.csproj -- verifyThe lab emits a readiness report with proven-locally, host-owned, deferred, unsafe-to-copy, and blocked rows. Its in-process host proof shows where IDurableTaskFlowRunner<TContext> and IDurableTaskFlowClient<TContext> fit, while keeping Durable Task worker/client hosting and storage provider setup explicitly host-owned.
For contributor verification, build the solution:
dotnet build
dotnet test --no-buildRun merged solution coverage for this repository's AppSurface-specific validation lane:
./scripts/coverage-solution.sh
dotnet run --project Cli/ForgeTrust.AppSurface.Cli -- coverage gate --coverage TestResults/coverage-merged/coverage.cobertura.xml --min-line 95 --min-branch 85 --diff-base origin/main --min-patch-line 95 --min-patch-branch 85This command:
- Runs each solution test project.
- Collects coverage only for
ForgeTrust.AppSurface.*modules. - Excludes test modules (
*.Testsand*.IntegrationTests) from coverage. - Produces one merged Cobertura file at
TestResults/coverage-merged/coverage.cobertura.xml. - Produces AppSurface-managed JUnit files as
TestResults/coverage-merged/junit-coverage-<index>-<project-name-hash>.xml. - Writes a summary to
TestResults/coverage-merged/summary.txt. - Writes machine-readable timing data to
TestResults/coverage-merged/timings.json. - Writes slow-test diagnostics to
TestResults/coverage-merged/slow-test-diagnostics.mdandTestResults/coverage-merged/slow-test-diagnostics.json, including diagnostic aggregation overhead in seconds and as a percent of elapsed runner time at diagnostics generation. - Uses the source AppSurface CLI and its package-owned ReportGenerator dependency for the default full-solution lane.
Private package-consuming repositories should use the public CLI runner instead of this repository's script:
dotnet tool run appsurface coverage run --solution ./MyApp.slnx
dotnet tool run appsurface coverage gate --coverage ./TestResults/coverage-merged/coverage.cobertura.xml --min-line 85 --min-branch 75The appsurface coverage run command discovers .sln/.slnx test projects or accepts repeated --test-project values, runs Coverlet-instrumented projects, writes private local artifacts under TestResults/coverage-merged, and merges Cobertura through the CLI package's ReportGenerator dependency without reading the consumer repo's tool manifest. No separate merge command is required for ordinary package consumers: coverage run produces TestResults/coverage-merged/coverage.cobertura.xml directly. Managed test results are opt-in with --test-results junit; this requires selected test projects to reference JunitXml.TestLogger. --slow-test-diagnostics implies managed JUnit results and writes slow-test-diagnostics.md and .json beside the merged coverage file. Use appsurface coverage merge --source ./TestResults/coverage-shards --output ./TestResults/coverage-merged when a matrix job or custom test workflow already produced shard files named coverage.cobertura.xml. The optional appsurface coverage gate command evaluates that merged Cobertura file locally, writes coverage-gate.json and coverage-gate.md, appends the Markdown report to $GITHUB_STEP_SUMMARY when GitHub Actions provides it, and fails with ASCOV020 when line, branch, or configured patch coverage is below threshold. Patch coverage accepts exactly one source: --diff-base for local Git history, --diff-file for a CI-produced unified diff artifact, or --diff-stdin for piped unified diff text. External diff artifacts are private local inputs, are bounded at 20 MiB, and fail closed when non-empty content is not unified diff text. The coverage commands are intentionally private-by-default: they do not upload coverage, call GitHub APIs, or store trends.
The script also supports bounded test groups for local or CI experiments:
BUILD_CONFIGURATION=Release BUILD_SOLUTION=false ./scripts/coverage-solution.sh --group web --output TestResults/coverage-groups/web
./scripts/coverage-solution.sh --list-groups
./scripts/coverage-solution.sh --merge-only TestResults/coverage-groups --output TestResults/coverage-mergedAvailable bounded groups are core, tools, web, docs, razorwire, and integration.
Default PR validation keeps solution coverage in one lane until measured group runs prove they reduce total GitHub Actions minutes, not just wall-clock time.
The current CI critical-path policy and timing baseline live in eng/ci-critical-path.md.
When tests need to launch child processes, prefer CliWrap-backed helpers over raw System.Diagnostics.Process setup. Keep process-output capture in the helper result so CI failures include stdout and stderr, and reserve raw Process usage for tests that intentionally exercise process-wrapper behavior.
Check out the examples to see how modules are composed in practice:
dotnet run --project examples/auth-aspnetcore-bridge
dotnet run --project examples/console-app
dotnet run --project examples/flow-approval-local/FlowApprovalLocalExample.csproj
dotnet run --project examples/product-readiness-lab/ProductReadinessLab.csproj -- --report
aspire run --non-interactive --apphost examples/product-readiness-lab-apphost/ProductReadinessLabAppHost.csproj -- verify
dotnet run --project examples/web-app
dotnet run --project examples/razorwire-mvc/RazorWireWebExample.csprojFor the intentional validation-failure shape, run dotnet run --project examples/config-validation.
The RazorWire MVC example includes a failed-form UX page at /Reactivity/FormFailures that shows server-handled validation, development anti-forgery diagnostics, default fallback rendering, and consumer styling hooks.
AppSurface is preparing to release the entire monorepo in unison. The public release contract now lives in the repository so teams can see what is queued for the next version, how pre-1.0 changes are handled, and where future migration notes will live.
- Package chooser - the generated first-install map for web, console, Aspire, and optional package add-ons.
- Release hub - start here for the narrative release surface.
- Unreleased proof artifact - the living notes for the next coordinated version.
- Changelog - the compact ledger for tagged and in-flight changes.
- Pre-1.0 upgrade policy - the stability and migration contract before
v1.0.0. - Contribution and release entry rules - how PR titles and unreleased entries feed the release surface.
AppSurface uses GitHub issue forms to keep bug reports, feature requests, and docs/developer-experience feedback concrete enough to reproduce or evaluate. If an example, README, quickstart, or package API leaves you stuck, start with the contribution guide, choose an issue template, and file the form that matches the problem.
Use docs/DX feedback for confusing guidance, missing concepts, broken links, snippet drift, or first-run friction. Use feature requests for focused product capabilities, API shapes, workflows, or examples. Use bug reports when runtime behavior, generated output, or package APIs do something unexpected. Do not file suspected vulnerabilities, leaked secrets, or exploit details in public issues; follow the security policy instead.
The examples directory contains sample applications that demonstrate how to use this project.
- Auth ASP.NET Core bridge example – proves an ASP.NET Core host-owned auth stack can flow named policy results into AppSurface auth contracts.
- Console app example – builds a simple command line application using CliFx source-generated command descriptors.
- Aspire AppHost example – shows local Aspire AppHost composition with AppSurface profiles and reusable Aspire components.
- Web app example – shows a minimal ASP.NET Core app that composes middleware and endpoints from modules.
- Web error-page proof – runs a one-command verifier for AppSurface Web browser status pages, production exception pages, and API-friendly non-HTML behavior.
- Config validation example – shows scalar validation on a strongly typed config wrapper and the startup failure shape.
- Local secrets example – shows OS-backed local secret posture, CLI setup, provider precedence, and paste-safe diagnostics for solo development before a remote vault exists.
- Flow approval local example – shows a typed flow that waits for an approval event and resumes through the in-memory runner.
- Product readiness lab – runs a composed
local evaluator with AppSurface Web, Auth.AspNetCore, Flow, DurableTask-facing
host-shape guidance, and Postgres product-state proof. Use its AppHost
verifyprofile when the report should exercise Postgres and require that row to beproven-locally.
AppSurface is licensed under the Polyform Small Business License 1.0.0.
Free for individuals and businesses with fewer than 100 people and under $1,000,000 USD prior-year revenue (inflation-adjusted). Larger companies require a commercial license.