Skip to content

Architecture policy reference

architecture.guardian.yaml is the versioned source of architecture rules for CLI and MCP analysis. It defines project ownership, modules, layers, public contracts, dependency permissions, rule selection, severities and the quality gate.

New users can start with the policy walkthrough. This reference covers YAML version 1; the JSON Schema supports editor validation. The YAML walkthrough explains how to grow a policy from a first layer boundary to module contracts and technology restrictions. Try the downloadable examples to observe passing and failing scans.

CLI scan and MCP analyze_solution use the same loader, ownership resolution and rule engine. Loading a valid policy alone establishes neither resolved project coverage nor compliance. Compiler integration reads this same YAML with build-supplied ownership metadata. Legacy XML remains supported during the transition release.

For a repository with the following four projects, place the policy beside src/:

architecture.guardian.yaml
src/Orders/Orders.Application.csproj
src/Orders/Orders.Domain.csproj
src/Billing/Billing.Contracts.csproj
src/Billing/Billing.Infrastructure.csproj

This complete example selects all six rules. Project references and C# uses still come from the loaded workspace; the YAML declares ownership and constraints only.

version: 1
modules:
- id: Orders
projects: ["src/Orders/**/*.csproj"]
allowedDependencies: ["contract:Billing/api"]
forbiddenNamespaces: [System.Net]
- id: Billing
projects: ["src/Billing/**/*.csproj"]
contracts:
- id: api
projects: ["src/Billing/Billing.Contracts.csproj"]
layers:
- id: Domain
projects: ["src/Orders/Orders.Domain.csproj"]
domainInfrastructure:
forbiddenNamespaces: [System.Data]
allowedNamespaces: [System.Data.SqlTypes]
- id: Contracts
projects: ["src/Billing/Billing.Contracts.csproj"]
- id: Application
projects: ["src/Orders/Orders.Application.csproj"]
allowedDependencies: [Domain, Contracts]
- id: Infrastructure
projects: ["src/Billing/Billing.Infrastructure.csproj"]
allowedDependencies: [Contracts]
rules:
AG001: error
AG002: error
AG003: warning
AG004: error
AG005: error
AG006: error
gate:
failOn: error

version, modules, layers and rules are required. The version is the integer 1. Modules, layers, project-selector lists and selected rules must be nonempty. Module and layer IDs are unique within their respective collections; contract IDs are unique within their module. IDs follow [A-Za-z][A-Za-z0-9_.-]* and compare ordinally, including case. Module and layer IDs may share a spelling.

contracts, allowedDependencies, forbiddenAssemblies and forbiddenNamespaces default to empty lists. Layers may also declare the optional domainInfrastructure object. gate defaults to failOn: error; when present it must contain failOn. Duplicate list values, duplicate YAML keys, missing fields, unknown fields and unknown rule IDs are errors. No policy inheritance, environment expansion, implicit exclusion or fallback policy is supported. Settings such as extends, enabled, off, baselines, allowIncomplete and custom rule options are rejected instead of ignored.

The schema supports editor validation through a comment such as:

# yaml-language-server: $schema=./docs/schema/architecture.guardian.v1.schema.json

The adapter enforces the structural schema in code, then validates identities and references in Core. Editor JSON Schema validation cannot establish referential integrity, selector coverage or ownership; always load and resolve before using a policy. YAML must be one tree-shaped data document. Aliases, anchors, merge keys, explicit tags and YAML version directives are unsupported. Quote glob strings; strings resembling null, booleans or numbers also require quotes. The loader accepts at most 32 nesting levels and 100,000 parser events and performs no network calls.

The host supplies the complete inventory for the requested analysis scope. Each identity is a project file path relative to the directory containing the policy. The identity is not the assembly name, MSBuild project name, namespace or a path relative to the current working directory. For the example above, src/Billing/Billing.Contracts.csproj is the contract project identity.

The loader takes an explicit file path without directory discovery. It does not read projects. The workspace adapter must calculate relative project paths and preserve their case. Incoming inventory backslashes normalize to /; policy selectors always use /. Leading/trailing slashes, drive prefixes, empty segments, . and .. segments and control characters are rejected. A project outside the policy directory cannot be represented by a parent traversal.

SelectorMeaning
src/Billing/Billing.csprojExact entire path
src/Billing/*.csproj* matches zero or more characters within that segment
src/Billing/Billing?.csproj? matches exactly one character within that segment
src/**/*.csprojA whole ** segment matches zero or more directories, including src/App.csproj
src/**All project paths below src/

Matching is anchored to the whole path and is ordinal/case-sensitive on Linux, Windows and macOS. Dots and other permitted characters are literal. Character classes, brace expansion, negation and embedded ** such as **.csproj are errors. There is no implicit **/ prefix, extension filter, path case folding, symlink resolution or filesystem enumeration. Selectors in one declaration form a union; matching two selectors within the same declaration does not create two owners.

Module identity is the explicit modules[].id, such as Billing. It is not inferred from a directory, assembly name, RootNamespace, namespace or solution folder. Two projects with different names can belong to one module; renaming an assembly does not change module membership when the project path and policy stay the same. Changing a module ID changes diagnostic targets and can make baseline findings new. Project moves require updating selectors and references together.

Every inventory project must map to exactly one module and exactly one layer. Modules and layers are independent dimensions: one module may span several layers, and one layer may contain projects from several modules. Use a single All layer if no finer layer distinction is needed.

Every declared selector must match at least one inventory project. Duplicate normalized inventory paths, an empty inventory, unmatched projects, overlapping modules or overlapping layers are errors. There is no first-match precedence, automatic module creation or silent ignore behavior. Missing projects due to loading failures must remain a failed/incomplete host outcome; a reduced inventory must not be represented as complete.

A public contract is an explicitly named set of whole projects within its module. Every selected contract project must also belong to that module. A project may belong to zero or one contract; overlapping contracts and selectors outside the owning module are errors. A project without a contract is an implementation project. V1 does not infer contracts from names such as *.Contracts, or define namespace, type or member selectors. Those cannot be substituted for project identities.

Resolution produces immutable ProjectPolicyMapping values, sorted by project identity. Declarations and input order do not change ownership or permissions. Errors include the affected project/selector and module/layer context.

Permissions are outgoing, direct and case-sensitive; they do not imply reciprocal or transitive access. Omission means no cross-boundary permission. References must resolve to declarations when loading the policy, even before an inventory exists.

PermissionResolution and effect
module:BillingExact declared module; permits AG001 project dependencies to all its projects, including implementation projects
contract:Billing/apiExact declared module plus local contract ID; permits AG001 dependencies only to projects in that contract
Contracts in a layer’s allowedDependenciesExact declared layer ID; permits AG002 dependencies to projects mapped to that layer

Bare module names, globs and undeclared targets in module permissions are errors. contract:Billing/api does not grant access to another api contract, to other Billing projects or to Billing’s dependencies. Intra-module and intra-layer project dependencies are allowed by their respective permission checks. Cycles are a separate rule (AG003); an explicit permission does not disable cycle discovery.

ResolvedPolicy.AllowsModuleDependency and AllowsLayerDependency answer separate questions about an already discovered project edge. A module permission cannot override a layer restriction. Unknown source/target project identities are errors. These APIs do not discover edges, produce violations, enforce semantic symbol access or decide a quality gate. AG001’s ModuleDependenciesRule attaches evidence and findings to module permission decisions; AG002’s LayerDependenciesRule does the same for layer permission decisions. Neither assumes fixed layer names or a universal Clean Architecture layout.

Modules and layers optionally declare forbiddenAssemblies and forbiddenNamespaces:

modules:
- id: Orders
projects: ["src/Orders/**/*.csproj"]
forbiddenAssemblies: [Legacy.Data]
forbiddenNamespaces: [System.Net]
layers:
- id: Domain
projects: ["src/**/*.Domain.csproj"]
forbiddenNamespaces: [Microsoft.EntityFrameworkCore]
rules:
AG005: error

This fragment belongs in a complete v1 document. Both lists default to empty and may be empty. Values must be unique. Assembly restrictions are exact, case-sensitive simple names using ASCII letters, digits, ., _ and -; no paths, versions, wildcards or NuGet package IDs are interpreted. Namespace restrictions are case-sensitive, dot-separated ASCII identifiers (letter or _ first, then letters, digits or _). Each restriction includes the exact namespace and its descendants: System.Net matches System.Net.Http, while System.NetLike passes. Do not use global::, @ escapes or wildcards in policy names.

A project’s module and layer restrictions apply independently; allowed dependencies, contracts and same-module ownership do not waive them. Framework, package, repository and same-project symbols are included. AG005 checks bound type/member uses, not strings, comments, unused imports or unused assembly/project references. Framework restrictions use their bound assembly identity, which can differ from a reference facade or package name. Existing policies remain valid. Select AG005 in rules to run it; declaring restrictions alone does not select a rule.

A layer’s optional domainInfrastructure object marks that layer as a source of AG006 checks. Membership comes from its project selectors; no layer name (including Domain) or technology is special. For example:

layers:
- id: Domain
projects: ["src/**/*.Domain.csproj"]
domainInfrastructure:
forbiddenAssemblies: [Legacy.Persistence]
forbiddenNamespaces: [Microsoft.EntityFrameworkCore, System.Data]
allowedNamespaces: [System.Data.SqlTypes]
rules:
AG006: error

Use this fragment inside a complete v1 document. All three lists are optional, unique and may be empty. Names and descendant matching follow AG005’s exact assembly and namespace rules above. An absent or empty object bans nothing. allowedNamespaces exempts bound target namespaces and descendants from both AG006 assembly and namespace bans, only for projects in this source layer. It does not exempt similarly prefixed namespaces or another layer. Namespace exceptions authorize all types and members in that namespace subtree, regardless of assembly; use a narrow namespace. Allowed domain abstractions outside the configured technology identities pass.

AG006 checks bound type/member evidence, including framework, package, repository and same-project symbols. It does not ban a technology merely because an unused reference or import exists. Module permissions, contracts and the layer dependency matrix do not waive these restrictions. Select AG006 to execute it; declaring the object alone does not select it. Exceptions do not override AG002 or AG005; see the overlap policy.

The rules mapping explicitly selects canonical IDs AG001–AG006. Omitted IDs are not selected. Every selected ID has exactly one severity: info, warning or error. Selection is separate from reporting severity; off and none are invalid severities. The rule catalog describes each identity.

ArchitecturePolicy.SelectRules selects registered implementations in ordinal ID order and rejects unavailable selected IDs before analysis. Catalog metadata is not an implementation. A host must never drop unavailable rules and present the remaining analysis as completed. Selecting a future rule in a valid document does not mean the current engine can execute it.

Rules discover severity-free violations. SeverityPolicy assigns their configured reporting levels after discovery without changing their identity or evidence. GatePolicy then evaluates the completed result separately. Its failOn threshold is inclusive: warning fails on warnings and errors; info fails on any diagnostic; error fails on errors. Incomplete, failed and cancelled analysis always fails the gate, regardless of threshold or diagnostic count. An empty completed result passes. CLI scan --baseline <path> applies the same threshold only to new findings. Baseline files are explicit command inputs; YAML v1 has no baseline setting. See diagnostic baselines.

Accepted debt is a separate, explicitly reviewed JSON inventory. Choose the gate mode at the command line; none of these modes permits missing coverage:

Command modeFindings that can fail the severity gate
scanAll current findings
scan --baseline <file>New findings after stable fingerprint matching against the explicit file
scan --base <commit-or-ref>New findings compared with a complete scan of the local Git base
baseline update ... --baseline <file>Saves all current findings after completed analysis; success accepts the inventory rather than proving a clean architecture

--base and --baseline cannot be combined. Scans never update accepted debt. Review changes to rule selection, ownership and baseline entries together; use separate baseline files for independently analyzed configurations/frameworks. See baseline matching and updates and Git comparison.

CLI exitMeaning
0The command completed successfully; a scan passed its configured gate, or an explicit baseline update saved its inventory
1The scan completed and a finding failed the applicable severity gate
2Invalid arguments/policy/baseline, unavailable context or rule, incomplete/failed analysis, cancellation or output failure

An error threshold can pass warnings, and accepted existing errors can pass a new-finding gate. Neither result means there are no findings. Incomplete analysis with zero findings exits 2. MCP exposes completion and gate outcome separately; compiler severity comes from editorconfig instead of this CLI exit contract.

Policy loading and Core evaluation do not fetch rules, schemas or help pages. The workspace must already have the SDK, reference packs, restored assets and generator inputs for the selected configuration/framework. See offline and deterministic operation for preparation and the limits of reproducibility, and trust and isolation before loading an untrusted repository. YAML restrictions constrain analyzed dependencies; they do not sandbox MSBuild tasks or source generators.

The netstandard2.0 compiler analyzer reads this same YAML through AdditionalFiles, using packaged MSBuild targets for project/assembly ownership metadata. It shares strict parsing and selector semantics with CLI/MCP. Selected compiler-supported rules use YAML default severities; .editorconfig can override or suppress their reporting. AG003 and the solution gate remain CLI/MCP responsibilities. Legacy XML is supported for one transition release; configure exactly one format and follow the migration guide.