Concrete examples of what Engineering Playbook does, and who each one is for.
Developer
A repository that satisfies every convention on its first build
You are starting a repository and want the project defaults, code style, test wiring and assembly identity to be right from the first commit, instead of discovering the conventions one review comment at a time.
{
"sdk": { "allowPrerelease": false },
"msbuild-sdks": { "Purview.BuildSdk": "1.0.4" },
"test": { "runner": "Microsoft.Testing.Platform" }
}
What you get: A pinned SDK plus a `Directory.Build.props` that sets one namespace prefix is the whole of the repository's build configuration. Every project beneath it inherits the target framework, language version, nullable context, analyzers and assembly identity, so a project file is two lines and a test project file is empty.
Getting Started walks the steps in the order you would create the files, and every repository-level file has a copy-paste version in the templates folder with a comment explaining the one thing about it that is easy to get wrong.
Read the guide →
Architect
Architecture that fails the build when a dependency points the wrong way
Your layering is documented and agreed, and you want it to stay true — rather than being eroded one convenient project reference at a time, each of which looks reasonable in isolation.
[Test]
public async Task Application_GivenItsReferences_DependsOnTheDomainAndNotOnInfrastructure()
{
// Arrange
var application = typeof(ConventionCatalogue).Assembly;
// Act
var referenced = ReferencedAssemblyNames(application);
// Assert
await Assert.That(referenced).Contains($"{RepositoryPrefix}.Domain");
await Assert.That(referenced).DoesNotContain($"{RepositoryPrefix}.Infrastructure");
}
What you get: The allowed dependency directions are data in a test that reads the compiled assemblies' metadata references, so a reference that points the wrong way fails the build with the offending assembly named — no diagram to keep in step and no separate tool to run.
The reference implementation's Architecture.UnitTests project asserts four rules against three real assemblies and is built by the same pipeline as everything else. The page also records the two traps found by writing it, including a source generator that emits public types into its own namespace.
Read the guide →
Team lead
A verification gate that stays credible
The pull-request build has started needing a database and four minutes of waiting, and somebody is about to make it optional — at which point nothing is gated at all.
{
"Build": {
"Solution": "src/Acme.slnx",
"TestRoot": "src/tests",
"TestPatterns": "*UnitTests.csproj",
"TestFilter": "/*/*/*/*[Category=Unit]"
}
}
What you get: Tiers are expressed by where a test lives rather than by an attribute: the SDK reads the test project's name suffix and emits the assembly-level category the pipeline filters on, so adding a slow suite keeps it out of the fast tier without editing a filter or remembering an attribute.
The verification model page documents the shared workflow's inputs, the configuration probe order, and the two ways a repository and the shared workflow quietly disagree about what a green build means — and names the repository that runs its container-backed suites as a second job on the same runner.
Read the guide →
Developer
One builder for every test's domain content
You are writing a test, need a domain object, and do not want to spell out a constructor that will change — nor leave the reader guessing which of the values you passed actually matter to the assertion.
public static Domain.Convention Convention(
Domain.ConventionId? id = null,
string? title = null,
VerificationTier? tier = null,
bool? enforcedByBuild = null) =>
Domain.Convention.Create(
id ?? ConventionId(),
title ?? Faker.Lorem.Sentence(4),
tier ?? Faker.Random.Enum<VerificationTier>(),
enforcedByBuild ?? Faker.Random.Bool());
What you get: An omitted argument is generated and a supplied argument is used exactly, so a test reads as a list of the values it is actually proving and everything else is realistic without the test saying so. One constructor change is one edit rather than a repository-wide one.
TestBuilder lives in a shared testing project the SDK references from every test project automatically. The page also explains the two SDK rules that shaped its flat, ungrouped shape, and the value-object exception where a test must name the rejected input itself.
Read the guide →
Contributor
A repository an agent can work in without hand-holding
You want a coding agent to make a change that follows the repository's conventions rather than confidently generalising from whichever pattern it found first.
| Path | Purpose |
| -- | -- |
| src/src/Domain | The reference implementation's value objects and aggregate |
| src/src/Application | The port and the service that owns the rules |
| src/tests/Architecture.UnitTests | Dependency-direction rules; a wrong reference fails the build |
What you get: AGENTS.md states the purpose, the layout, the conventions and the definition of done, and the build mirrors the SDK's own skills into the repository so the agent-facing guidance tracks the SDK version rather than the repository's memory of it.
The agent enablement page documents the layout of the agent folder, why the SDK's mirror writes a generated .gitignore that lists exactly the files it owns, and why the guidance that makes a repository obvious to an agent is the same guidance that makes it obvious to a new joiner.
Read the guide →
Architect
Take only the part of the playbook that fits
You are convinced by some of the conventions and not others, and you want to know which ones are worth the cost in a repository the size of yours — rather than adopting everything and discovering the ceremony.
What you get: Three profiles — one project, a few layers, several packages — each list what to take and what to leave, and every page ends with an "at your scale" section that names the smaller version of the rule explicitly. The templates are mapped to the profile that needs them.
The playbook also documents what not to adopt early, with the reason each one is more expensive out of order, and a migration order for an existing repository that keeps each step cheap and the build green.
Read the guide →
Team lead
Data that crosses a boundary once, and is trusted after that
Inbound payloads are validated in several places, an identifier is a string in one layer and a type in another, and expected failures are thrown as exceptions that callers catch by message.
[Scalar]
public readonly partial record struct EmailAddress
{
public string Value { get; }
static partial void OnNormalize(ref string value) => value = value?.Trim().ToLowerInvariant()!;
}
What you get: A value is a type rather than a primitive, `Create` validates at the boundary and `Hydrate` trusts everything past it, normalisation runs before validation so there is exactly one canonical form, and expected failures are values while exceptional circumstances still throw.
The contracts page names the three public repositories that implement the rules — source-generated value objects, result types with union errors, and a schema layer that also exports and imports JSON Schema — and shows how a validation result is read rather than parsed.
Read the guide →
Contributor
Extend the playbook without turning it into a second source of truth
You have found a convention that is real, enforced and not written down, and you want to add it without duplicating something the SDK already documents.
What you get: Four tests decide whether a rule belongs: it came from a real repository and a real failure, it is reusable at more than one size, it is not the SDK's to state, and it is either enforced or explicitly labelled a review obligation.
The contributing page also sets the expectations for changing an existing rule — update every page that states it, check the reference implementation still demonstrates it, and re-check whether the enforcement moved between the build and review.
Read the guide →