feat(contract): implement RIX — relationship-aware architecture contract with reverse indexes#22
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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 onConstraintsContractusesdefault_factoryso all existing construction sites are unaffected. It is excluded from the content hash since it is computed state, not authored policy._compute_relationsinConstraintsContractBuilder(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_referencesalready operates. Unresolved references are silently dropped (already warned earlier inbuild_contract). Supersession chains walk newest→root then reverse; a visited set prevents infinite loops on circular edges.PlanningWorkflow + MCP surface (
context/planner.py,mcp/server.py) —_get_adr_relations()pulls per-ADR slices fromcontract.relations. Each ADR dict in_find_relevant_adrsgains arelationskey. The MCP response carriesrelations_summary(fullContractRelationsdict) alongside existing fields. Additive — no existing response keys changed.Key trade-off:
relations_summarycan 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
ConstraintsContractconstruction sites remain unaffected (norelationsargument required anywhere).Risks
Additive changes only — no existing response consumers are affected. The
relationsfield onConstraintsContractdefaults to an emptyContractRelations(), so serialized contracts without the field deserialize cleanly. The only visible change to callers is new keys inadr_planning_contextresponses.