Contract Validation (Invariant 5)#
Status#
Field |
Value |
|---|---|
Status |
Implemented |
Version |
1.0 |
Date |
2026-04-11 |
Issue |
GH-320 (PR #335) |
Problem#
When contract-first decomposition generates interface contracts for multiple domains, those contracts may define the same named type with incompatible field types. For example:
Weather service contract:
WidgetPosition { x: int, y: int, width: int }Time widget contract:
WidgetPosition { x: string, y: string, width: string }
If both contracts reach implementation agents, each agent writes code that compiles against its own contract but fails at integration. The type mismatch is invisible until runtime.
Solution#
check_contract_cross_file_consistency in src/integrations/contract_validation.py scans all in-memory contract artifacts for type contradictions before decomposition proceeds. This is called Invariant 5 and is the only hard gate in the contract-first pipeline.
How It Works#
The function receives the contract artifacts dict (keyed by domain name) and:
Filters to interface contracts by filename pattern: keeps only artifacts where
"interface-contracts"appears in the filename. This is a deliberate choice over filtering byartifact_typebecause the live generator emitsartifact_type="specification"for interface contracts (a naming mismatch caught by Codex P1 review).Extracts type definitions from each contract document. Types are identified by common patterns:
class/interface/type/structdeclarations, TypeScript interfaces, Python dataclasses, and Pydantic models.Cross-references types across domains. When the same type name appears in multiple domains, the function compares field names and field types.
Reports contradictions. A contradiction exists when two domains define a type with the same field name but different field types. Field presence differences (domain A has a field that domain B lacks) are not contradictions – they indicate one contract is a superset, which is safe.
Gate Behavior#
check_contract_cross_file_consistency(contract_artifacts)
├── No contradictions found → returns success, pipeline continues
└── Contradictions found → returns failure with details
└── _try_contract_first_decomposition falls back to feature-based
The gate is binary: any type contradiction triggers fallback. There is no “partial accept” mode because a single type mismatch can cascade through the entire integration boundary.
Why Only Type Contradictions#
Invariant 5 checks only for type contradictions, not for:
Missing types (a domain references a type no other domain defines)
Missing verbs (contracts omit user-facing actions from the PRD)
Incomplete coverage (contracts do not cover all requirements)
These are quality issues, not correctness failures. A missing verb produces a product that lacks a feature; a type contradiction produces a product where two modules cannot communicate. The first is fixable by the integration agent; the second is not.
A verb-coverage gate was proposed and rejected during GH-320 review. A six-verb hard-coded checklist was too brittle (false positives on non-UI projects) and too destructive (discarding the 55/45 coordination win). See Contract-First Decomposition for the full rationale.
What Invariant 5 Does NOT Catch#
Semantic contradictions: domain A says “temperature in Celsius” while domain B assumes Fahrenheit. These are natural-language disagreements that text parsing cannot detect reliably.
Behavioral contradictions: domain A expects synchronous calls while domain B provides async-only APIs. These require deeper analysis than type comparison.
Schema shape differences: domain A nests
positioninsidewidgetwhile domain B uses a flat structure. Both may defineWidgetPositionconsistently but use it differently.
These gaps are acknowledged. Invariant 5 catches the most common and most dangerous class of cross-contract error. Future invariants may extend coverage.
Implementation#
Module#
src/integrations/contract_validation.py
Public API#
def check_contract_cross_file_consistency(
contract_artifacts: dict[str, list[dict[str, str]]]
) -> tuple[bool, list[str]]:
"""
Check generated contracts for type contradictions across domains.
Parameters
----------
contract_artifacts : dict[str, list[dict[str, str]]]
Contract artifacts keyed by domain name. Each value is a list
of artifact dicts with "filename" and "content" keys.
Returns
-------
tuple[bool, list[str]]
(is_consistent, contradiction_descriptions).
is_consistent is True when no type contradictions are found.
contradiction_descriptions lists human-readable descriptions
of each contradiction found.
"""
Integration Point#
Called inside _try_contract_first_decomposition in src/integrations/nlp_tools.py, between contract generation and decompose_by_contract:
is_consistent, contradictions = check_contract_cross_file_consistency(
contract_artifacts
)
if not is_consistent:
logger.warning(
"Contract cross-file inconsistency detected, "
"falling back to feature-based: %s",
contradictions,
)
return self._feature_based_fallback(...)
Test Coverage#
tests/unit/integrations/test_contract_validation.py – 7 tests:
Consistent contracts pass validation
Type contradictions are detected across two domains
Type contradictions are detected across three or more domains
Field presence differences (superset) do not trigger contradiction
Non-interface-contract artifacts are ignored
Empty contract artifacts pass validation
Single-domain contracts pass validation (no cross-reference possible)
See Also#
Contract-First Pipeline – Full pipeline specification
Contract-First Decomposition – Conceptual overview and decisions
Quality Assurance – Marcus’s broader quality framework