Interface Implementation Contracts¶
Interface implementation contracts restrict where selected interfaces may be implemented: application ports implemented only by infrastructure/adapters, infrastructure abstractions never implemented by domain types, UI layers never implementing domain internals directly. This is a static type-relationship check on the compiled interface set, not runtime dependency-injection resolution.
Groups:
strict_interface_implementationaudit_interface_implementation
Example¶
contracts:
strict_interface_implementation:
- id: ports-implemented-only-by-adapters
name: application-ports-implemented-only-by-adapters
interface_prefixes: [MyApp.Application.Ports.]
allowed_only_in_layers: [infrastructure]
reason: Port implementations belong to the infrastructure boundary.
When to use¶
Use interface implementation contracts when an interface's implementations should be confined to (or excluded from) a specific part of the codebase:
- application ports (
MyApp.Application.Ports.*) may be implemented only in infrastructure/adapter layers; - infrastructure abstractions must not be implemented by domain types;
- presentation/UI types should not implement domain-internal interfaces directly.
For forbidding class inheritance from framework base types, use inheritance contracts instead.
Semantics¶
Selecting interfaces¶
interfaces is a list of exact, fully-qualified interface names matched by ordinal equality. interface_prefixes is a list of namespace/type-name prefixes matched by ordinal StartsWith — use it to select every port in a namespace without listing each one.
A contract must declare at least one of interfaces or interface_prefixes. A contract with neither is rejected at policy load time.
A non-interface type (class or struct) matches when any interface in its full interface set matches a selector — including interfaces implemented indirectly through a base class (a subclass of an adapter that implements a port is itself an implementation of that port). Constructed generic interfaces are matched by their generic type definition's CLR name (e.g. MyApp.Application.Ports.IRepository`1). Each distinct matched interface produces at most one match per type.
Interface types themselves are never candidates: an interface extending a selected interface is a contract extension, not an implementation escaping the boundary.
Location expectations¶
allowed_only_in_layers, allowed_only_in_namespaces, allowed_only_in_projects, and allowed_only_in_assemblies together form an allow-list: a matched implementation whose type's location does not satisfy at least one entry across all four lists is a misplaced violation.
forbidden_in_layers, forbidden_in_namespaces, forbidden_in_projects, and forbidden_in_assemblies together form a deny-list: a matched implementation whose type's location satisfies any entry across all four lists is a forbidden violation.
A contract must declare at least one allow-list entry or at least one deny-list entry; a contract with neither is rejected at policy load time.
If a single matched implementation fails both checks, exactly one violation is reported, described as forbidden (the more specific rule).
allowed_only_in_projects/forbidden_in_projects resolve each configured project name to its assembly name via project discovery — the same assembly-name-equivalence semantics documented for type_placement's must_reside_in_projects.
Violations¶
Each violation identifies the implementing type, the matched interface's fully-qualified name, whether the violation is misplaced or forbidden, the actual location, and — for misplaced violations only — the expected (allow-list) location description. Violations are emitted deterministically: implementing types ordered by fully-qualified name, matched interfaces within a type ordered by name (both ordinal).
ignored_violations entries use the same source_type/forbidden_reference/reason shape as other contract families, matching the implementing type and the matched interface's fully-qualified name.
Non-goals¶
- Not runtime DI resolution. The contract sees which types implement which interfaces in compiled metadata; it does not know which implementation a container actually resolves.
- No required-implementation rules ("interface X must have at least one implementation in layer Y").
- No regex or expression-language matching — only exact fully-qualified names and prefix matching.