Version
codebase-memory-mcp dev
Platform
Linux (x64)
What happened?
Explicit OVERRIDE detection misses a method when the declaring contract is inherited through an intermediate class that does not redeclare the method.
This leaves a valid runtime implementation disconnected from the contract method even though the class inheritance chain and the canonical base-typed call are both present.
Minimal reproduction
from abc import ABC, abstractmethod
class Processor(ABC):
@abstractmethod
def process(self) -> str:
raise NotImplementedError
class IntermediateProcessor(Processor):
pass
class LeafProcessor(IntermediateProcessor):
def process(self) -> str:
return "leaf"
def run(processor: Processor) -> str:
return processor.process()
Run a full index and inspect the relevant edges.
The graph correctly contains:
IntermediateProcessor -INHERITS-> Processor
LeafProcessor -INHERITS-> IntermediateProcessor
run -CALLS-> Processor.process
confidence=0.90
strategy=lsp_method
But neither of these queries returns an OVERRIDE edge:
MATCH (impl:Method)-[r:OVERRIDE]->(base:Method)
WHERE impl.qualified_name CONTAINS 'LeafProcessor.process'
RETURN impl.qualified_name, base.qualified_name
MATCH (impl:Method)-[r:OVERRIDE]->(base:Method)
WHERE base.qualified_name CONTAINS 'Processor.process'
RETURN impl.qualified_name, base.qualified_name
A direct-inheritance control works:
class DirectProcessor(Processor):
def process(self) -> str:
return "direct"
That produces:
DirectProcessor.process -OVERRIDE-> Processor.process
All fixture files reported no_recorded_issue from check_index_coverage.
Likely cause
On current main, cbm_pipeline_override_explicit() iterates direct IMPLEMENTS / INHERITS edges and override_match_methods() compares the child class's methods only with methods declared on that immediate base.
For LeafProcessor, the immediate base is IntermediateProcessor, which declares no process method. The search stops there and never reaches Processor.process.
Expected behavior
LeafProcessor.process should be connected to the inherited declaration it overrides:
LeafProcessor.process -OVERRIDE-> Processor.process
The override builder should walk the ancestor chain until it finds the nearest matching declaration, with cycle protection and deduplication. If the graph intentionally stores all overridden declarations rather than only the nearest declaration, that policy should be explicit and consistently tested.
Impact
Any polymorphic call expansion based on:
caller -CALLS-> contract <-OVERRIDE- implementations
will omit leaf implementations behind non-overriding intermediate classes. Inbound caller tracing, impact analysis, and override-set queries therefore under-report valid implementations.
This is independent of receiver inference: the CALLS edge to the contract is already correct.
Regression test
A graph-level test should assert all of the following:
- direct override:
DirectProcessor.process -> Processor.process
- indirect override through an empty intermediate:
LeafProcessor.process -> Processor.process
- no duplicate
OVERRIDE edges
- inheritance cycles or malformed hierarchies terminate safely
Related
Confirmations
Version
codebase-memory-mcp devPlatform
Linux (x64)
What happened?
Explicit
OVERRIDEdetection misses a method when the declaring contract is inherited through an intermediate class that does not redeclare the method.This leaves a valid runtime implementation disconnected from the contract method even though the class inheritance chain and the canonical base-typed call are both present.
Minimal reproduction
Run a full index and inspect the relevant edges.
The graph correctly contains:
But neither of these queries returns an
OVERRIDEedge:A direct-inheritance control works:
That produces:
All fixture files reported
no_recorded_issuefromcheck_index_coverage.Likely cause
On current main,
cbm_pipeline_override_explicit()iterates directIMPLEMENTS/INHERITSedges andoverride_match_methods()compares the child class's methods only with methods declared on that immediate base.For
LeafProcessor, the immediate base isIntermediateProcessor, which declares noprocessmethod. The search stops there and never reachesProcessor.process.Expected behavior
LeafProcessor.processshould be connected to the inherited declaration it overrides:The override builder should walk the ancestor chain until it finds the nearest matching declaration, with cycle protection and deduplication. If the graph intentionally stores all overridden declarations rather than only the nearest declaration, that policy should be explicit and consistently tested.
Impact
Any polymorphic call expansion based on:
will omit leaf implementations behind non-overriding intermediate classes. Inbound caller tracing, impact analysis, and override-set queries therefore under-report valid implementations.
This is independent of receiver inference: the
CALLSedge to the contract is already correct.Regression test
A graph-level test should assert all of the following:
DirectProcessor.process -> Processor.processLeafProcessor.process -> Processor.processOVERRIDEedgesRelated
OVERRIDErelation before traversal.Confirmations