Contextual Dependency Contracts¶
Contextual dependency contracts check that a discovered (role, metadata) selector match does not
reference another discovered (role, metadata) selector match — directly, without declaring a
layers.<name> for either side.
Groups:
strict_context_dependenciesaudit_context_dependencies
Use this family when a boundary is a business-context distinction (e.g. "Sales" vs. "Inventory")
rather than a fixed namespace or a hand-declared layer, and the boundary is discovered through
classification.attributes/classification.assembly_attributes role/metadata extraction. See
Semantic classification for how role/metadata is
discovered, and the comparison table there for when to use this family instead of
layers.<name>.selector.
Example¶
contracts:
strict_context_dependencies:
- id: sales-no-inventory
name: sales-must-not-depend-on-inventory
source:
role: DomainLayer
metadata:
domain: Sales
forbidden:
- role: DomainLayer
metadata:
domain: "!{source.metadata.domain}"
exclude:
- role: SharedKernel
reason: Bounded contexts must not depend on each other's domain types.
This contract matches every type whose resolved role/metadata satisfies source, then flags any of
its direct references that match a forbidden selector — here, any other DomainLayer type whose
domain differs from the currently-checked source type's own domain (the not-equal-to-source
operator). A candidate that also matches an exclude selector (here, SharedKernel) is removed from
consideration before violation evaluation.
Fields¶
| Field | Required | Meaning |
|---|---|---|
name |
yes | Human-readable contract name. |
id |
no | Stable identifier for --contract, baselines, and ignored-violation matching. |
source |
yes | A contextual selector matched against candidate source types. |
forbidden |
yes, non-empty | A list of contextual selectors; a source-matching type's reference to any matching target is a violation. |
exclude |
no | A list of contextual selectors; a candidate target matching any entry is removed from consideration before violation evaluation. |
ignored_violations |
no | Same shape and semantics as the existing dependency family: source_type/forbidden_reference/reason, matched by concrete type name. |
reason |
no | Human-readable rationale surfaced in diagnostics and generated baselines. |
Contextual selectors and metadata operators¶
Every source/forbidden/exclude entry is a contextual selector: role (required,
exact-match) plus an optional metadata map. Each metadata value is interpreted by exactly one of
four fixed operators, checked in this order:
| Form | Operator | Meaning |
|---|---|---|
| YAML sequence | in |
Matches if the type's resolved value equals any listed entry. |
"*" |
any |
Matches any resolved value, provided the key is present. |
"!{source.metadata.<key>}" |
not-equal-to-source |
Matches when the candidate's resolved value for <key> differs from the current source type's own resolved value for <key>. Only meaningful on forbidden/exclude — a source selector has no other source to reference. |
| anything else | exact |
Literal scalar match. |
A missing role, or a missing constrained metadata key, is a non-match — never an error. See Semantic classification for how metadata values themselves are extracted and canonicalized.
CEL predicates¶
Contextual source/forbidden/exclude selectors accept an optional when
field. when is additive to role/metadata: a candidate matches only if
the literal constraints already match and when evaluates to true.
source.when compiles against a source context (the same subject shape
used by layer selectors). forbidden[*].when/exclude[*].when compile
against a context whose schema declares source, target, and dependency
— enabling cross-context comparisons such as:
contracts:
strict_context_dependencies:
- name: sales-must-not-depend-on-other-domain
source:
role: Domain
forbidden:
- role: Domain
when: target.metadataText["domain"] != source.metadataText["domain"]
reason: Bounded contexts must not depend on each other's domain types.
- existing
roleandmetadataentries remain literal; only the explicitwhenfield carries a CEL predicate; - a selector with no
whenbehaves exactly as before this field existed; - a
whenevaluation failure fails the run as a policy/configuration error — for bothstrict_context_dependenciesandaudit_context_dependencies— rather than being treated as a non-match or silently ignored, and is never suppressed by baseline; - when a matching
forbiddenselector declaredwhen, the violation's evidence names that expression's source text alongside the existing role/metadata evidence.
dependency is reserved, not usable, in this release. The schema
declares dependency (kind, viaMethodBody, sourceMemberName,
targetMemberName) on the target/exclude context, but the runtime does not
yet populate these with real per-edge data — every candidate would resolve
to the same fixed values regardless of the actual reference, so a predicate
reading them would silently never behave as intended. Policy loading
therefore rejects any when at a forbidden[*]/allowed[*]/exclude[*]
location that references the word dependency at all, including inside a
string literal or comment, with an actionable error — do not author
dependency.* until real per-edge facts or a documented alternative ships.
Express constraints using source/target instead.
Exclude vs. ignored_violations¶
exclude is a pre-match filter on candidate targets — a target matching an exclude selector
never becomes a violation candidate at all. ignored_violations is the existing post-violation
suppression mechanism, keyed on concrete source/target type names, unchanged from the dependency
family. Use exclude for a structural exemption (e.g. "SharedKernel is always allowed"); use
ignored_violations for narrow, named debt.
Diagnostics¶
A contextual dependency violation's diagnostic carries the source type's resolved role and metadata,
the target type's resolved role and metadata, and matched_selector: forbidden — evidence a
namespace/layer dependency violation does not carry. Human output tags the finding
(kind: context_dependency, ...); JSON/CI-artifact output includes source_role, source_metadata,
target_role, target_metadata, and matched_selector fields, so a contextual violation is always
distinguishable from an existing namespace/layer dependency violation.
Strict vs. audit¶
Same semantics as every other family: strict_context_dependencies fails the build,
audit_context_dependencies reports without failing (though the CLI process itself still exits
non-zero for audit findings under --mode audit — see
Exit codes; CI opts out with continue-on-error: true, not by relying on
a zero exit code).
Baselines¶
strict_context_dependencies/audit_context_dependencies participate in baseline generation,
merge, and comparison identically to the dependency family — see
Migration baselines.