ContractGuard.MSBuild 0.0.3-alpha

Prefix Reserved
This is a prerelease version of ContractGuard.MSBuild.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package ContractGuard.MSBuild --version 0.0.3-alpha
                    
NuGet\Install-Package ContractGuard.MSBuild -Version 0.0.3-alpha
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ContractGuard.MSBuild" Version="0.0.3-alpha">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ContractGuard.MSBuild" Version="0.0.3-alpha" />
                    
Directory.Packages.props
<PackageReference Include="ContractGuard.MSBuild">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ContractGuard.MSBuild --version 0.0.3-alpha
                    
#r "nuget: ContractGuard.MSBuild, 0.0.3-alpha"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ContractGuard.MSBuild@0.0.3-alpha
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ContractGuard.MSBuild&version=0.0.3-alpha&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ContractGuard.MSBuild&version=0.0.3-alpha&prerelease
                    
Install as a Cake Tool

ContractGuard

A CI gate for .NET API surfaces. An architect prescribes the method signatures a team (or an AI coding agent) must honor; developers implement the bodies however they like; the build fails if any prescribed signature drifts.

Contracts are data, not code: decomposed signature elements in a JSON file that lives in the repo, validated by a schema, reviewed like any other change. Put the contract under CODEOWNERS and the architect-approval workflow comes for free — and because the gate reads the built assembly's metadata (no code execution, no analyzers a developer can switch off), it verifies the artifact that actually ships.

New to it? Start with the owner's manual — quickstart, the settings in plain language, and what the diagnostic IDs mean.

Status

Early scaffold. Core engine, CLI, and MSBuild gate work end to end. See What the gate can and can't see for the current enforcement boundary; the master list of gaps lives as TODOs in code.

How it works

samples/MyCompany.Orders.contract.json   what a contract looks like
schema/contractguard.schema.json         draft-07 JSON Schema ($schema gives editor red squiggles)

A contract names one assembly, the types it governs, and the members each type must expose:

{
  "$schema": "https://raw.githubusercontent.com/lxman/ContractGuard/main/schema/contractguard.schema.json",
  "assembly": "Shop.Domain",
  "types": [
    {
      "type": "Shop.Calc",
      "kind": "class",
      "members": [
        { "kind": "method", "name": "Add", "returns": "int",
          "params": [["int", "a"], ["int", "b"]] }
      ]
    }
  ]
}

Policy lives inside the contract (settings: exact-vs-open surface, parameter-name significance, accessibility scope...) so strictness cannot be weakened from a CI flag — changing policy means changing the reviewed file.

Use it

CLI (dotnet tool install -g ContractGuard --prerelease):

contractguard extract   --assembly Shop.Domain.dll --output Shop.Domain.contract.json
contractguard verify    --contract Shop.Domain.contract.json --assembly Shop.Domain.dll
contractguard show      --contract Shop.Domain.contract.json
contractguard add       --contract Shop.Domain.contract.json --type OrderService "public Task<Result> Submit(Order order)"
contractguard import    --contract Shop.Domain.contract.json IOrderContract.cs
contractguard normalize --contract Shop.Domain.contract.json --check

extract takes --scope public,protected,internal,private to pull out more than the default public+protected surface — prescribed members of any accessibility are enforced either way; scope governs what the deny sweeps and extraction consider surface.

extract bootstraps a contract from a golden build; verify is the gate (exit 0 pass, 1 violations, 2 errors); show renders the elements back as C# declarations. The authoring verbs go the other way: add decomposes a C# declaration string into elements (the contract file never stores C# text), import decomposes a whole scaffold file - an interface control document the architect wrote - and normalize rewrites a contract to canonical form (--check makes it a CI lint).

MSBuild package (the drop-in):

<PackageReference Include="ContractGuard.MSBuild" Version="0.0.3-alpha" PrivateAssets="all" />

After every build, <project>/<AssemblyName>.contract.json is verified automatically — violations land in the IDE error list pointing at the contract file. Projects without a contract file are skipped, so the reference can live solution-wide in Directory.Build.props. In CI, build with -p:ContractGuardRequireContract=true so a deleted contract file fails the build instead of silently removing the gate.

What the gate can and can't see

The gate reads assembly metadata, so its enforcement boundary is metadata's boundary. Two kinds of limits apply — ones that are permanent, and ones that are just not built yet.

Identical in metadata — by design, permanent

  • = default on a struct parameter and = null on a reference parameter compile to the same constant (a nullref). The gate therefore treats the JSON forms "default": null and {"$special": "default"} as interchangeable, and extract emits null for both. This can never produce a false pass: a given parameter type only admits one of the two meanings.
  • async does not exist in a binary signature. It is an implementation detail an implementer may freely add or remove, which is why it is deliberately absent from the contract vocabulary. Prescribe Task<T> Submit(...); whether the body is async is not the architect's business.

Decoded from attribute metadata

  • Nullable reference annotations (NullableAttribute/NullableContextAttribute) are decoded, so nullableAnnotations: significant enforces string vs string? for real. The default stays ignored — oblivious (pre-nullable) assemblies carry no annotations, and a mixed-context shop turning this on should do so deliberately. int? is a real type, Nullable<int>, and is always enforced regardless.
  • Tuple element names (TupleElementNamesAttribute) are decoded and significant by default — renaming (int x, int y) to (int a, int b) breaks consumers using named access.
  • Record classes are detected (the EqualityContract compiler pattern) and compare as "kind": "record"; the synthesized plumbing (EqualityContract, PrintMembers) is not governable surface, while public synthesized members (Equals, operators, Deconstruct) are. Record structs have no metadata marker and stay struct.
  • ref readonly returns and parameters and volatile fields decode from their modreqs/attributes.
  • notnull and class? constraints decode when nullableAnnotations is significant.
  • Enum parameter defaults written as "OrderStatus.Pending" resolve against enums defined in the scanned assembly. Enums from other assemblies still need the underlying numeric value — the gate never loads foreign assemblies.

Not decoded yet — accepted by the schema, but not enforced

  • Nullability on constraint types and inheritance. where T : IFoo? and annotations on base types and implemented interfaces are not decoded.
  • Explicit interface implementations are skipped and cannot be governed yet.
  • significantAttributes is accepted by the schema but attribute comparison is not implemented.

Building

dotnet build
dotnet test
dotnet pack src/ContractGuard.MSBuild -c Release

Requires the .NET 8 SDK or later. Tests compile C# snippets in-memory with Roslyn and read the emitted PE bytes back through the metadata reader — the same harness that will pin metadata-vs-ISymbol front-end consistency when the Roslyn analyzer (phase 2) lands.

ContractGuard eats its own cooking: ContractGuard.Core's public surface is governed by its own contract through the published ContractGuard.MSBuild package, so a PR that drifts the API fails its own gate.

License

This project is licensed under the MIT License - see the LICENSE file for details.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.10-alpha 78 6/13/2026
0.0.9-alpha 76 6/12/2026
0.0.8-alpha 74 6/12/2026
0.0.7-alpha 66 6/12/2026
0.0.6-alpha 60 6/12/2026
0.0.5-alpha 64 6/12/2026
0.0.4-alpha 91 6/12/2026
0.0.3-alpha 68 6/12/2026
0.0.2-alpha 83 6/12/2026
0.0.1-alpha 72 6/12/2026