Skip to content

feat(contract): implement RIX — relationship-aware architecture contract with reverse indexes#22

Merged
kschlt merged 3 commits into
mainfrom
feat/contract-relationship-indexes
Apr 4, 2026
Merged

feat(contract): implement RIX — relationship-aware architecture contract with reverse indexes#22
kschlt merged 3 commits into
mainfrom
feat/contract-relationship-indexes

Conversation

@kschlt

@kschlt kschlt commented Apr 4, 2026

Copy link
Copy Markdown
Owner

Why

The architecture contract was a one-directional view: each ADR declared its relationships (depends on, supersedes) but callers couldn't invert those edges. An agent or tool wanting to answer "what depends on ADR-007?" or "what does ADR-012 supersede?" had to scan all ADRs. RIX (Relationship Index) computes the full graph at build time so those questions are O(1) lookups.

Approach

Three layered commits, each independently reviewable:

  1. ContractRelations model (contract/models.py) — Pydantic model holding forward indexes (depends_on, related_to, supersedes), their reverses (required_by, related_from, superseded_by), supersession chains (oldest→newest), and clause lookup tables (clause_to_adr, adr_to_clauses). The field on ConstraintsContract uses default_factory so all existing construction sites are unaffected. It is excluded from the content hash since it is computed state, not authored policy.

  2. _compute_relations in ConstraintsContractBuilder (contract/builder.py) — wired after policy merge. All parsed ADRs (not just accepted ones) are used so edges to proposed or deprecated ADRs are preserved — consistent with how _validate_relation_references already operates. Unresolved references are silently dropped (already warned earlier in build_contract). Supersession chains walk newest→root then reverse; a visited set prevents infinite loops on circular edges.

  3. PlanningWorkflow + MCP surface (context/planner.py, mcp/server.py) — _get_adr_relations() pulls per-ADR slices from contract.relations. Each ADR dict in _find_relevant_adrs gains a relations key. The MCP response carries relations_summary (full ContractRelations dict) alongside existing fields. Additive — no existing response keys changed.

Key trade-off: relations_summary can be verbose for large corpora. Accepted because (a) it is additive and no current callers are affected, and (b) the per-ADR slice keeps the most common query cheap without needing the global view.

What Was Tested

  • 31 new unit tests across 3 test files covering: forward/reverse index construction, supersession chain ordering, circular-reference safety, unresolved-reference handling, clause lookup accuracy, and MCP response shape with relations data present.
  • Full suite: 617 passed (quality run above).
  • Verified that all existing ConstraintsContract construction sites remain unaffected (no relations argument required anywhere).

Risks

Additive changes only — no existing response consumers are affected. The relations field on ConstraintsContract defaults to an empty ContractRelations(), so serialized contracts without the field deserialize cleanly. The only visible change to callers is new keys in adr_planning_context responses.

kschlt added 3 commits April 4, 2026 17:52
…Contract

First of three steps to make the architecture contract relationship-aware.
ContractRelations holds forward indexes (depends_on, related_to, supersedes),
their reverse counterparts, derived supersession_chains, and clause lookup tables
(clause_to_adr, adr_to_clauses). The relations field on ConstraintsContract uses
default_factory so all existing construction sites are unaffected. The field is
excluded from the content hash because it is computed state, not authored policy.
A separate model was preferred over inline embedding for clarity and testability.
Implements _compute_relations so the compiled ConstraintsContract now
carries forward indexes (depends_on, related_to, supersedes), their
reverses (required_by, related_from, superseded_by), supersession chains,
and clause lookup tables (clause_to_adr, adr_to_clauses).

All parsed ADRs (not just accepted ones) are used so that edges to
proposed or deprecated ADRs are preserved — consistent with how
_validate_relation_references already uses the full corpus. Unresolved
references are silently dropped (already warned earlier in build_contract).
Supersession chains are built from newest root backward then reversed to
read oldest→newest; a visited set prevents infinite loops on circular
edges.
…planning_context response

Completes the RIX item by making relationship data accessible to MCP callers.
Added _get_adr_relations() helper to PlanningWorkflow that pulls per-ADR
relationship slices (depends_on, required_by, related_to, related_from,
supersedes, superseded_by) from the precomputed contract.relations indexes.
Each ADR dict in _find_relevant_adrs is now enriched with a 'relations' key.
After execute(), result.data carries 'contract_relations' (ContractRelations
instance) which the MCP layer serializes as 'relations_summary' in the
success_response data dict. Both the global view (cross-ADR impact analysis)
and per-ADR view (focused per-decision context) are included. Covered by 5
new unit tests (test_planning_relations.py); full suite: 502 passed.
@kschlt kschlt merged commit bcc52a6 into main Apr 4, 2026
8 checks passed
@kschlt kschlt deleted the feat/contract-relationship-indexes branch April 4, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant