Skip to content

Commit 471ec4c

Browse files
tae2089claude
andcommitted
Drop Bounded* analyzer interfaces and their dead fallback branches
BoundedImpactAnalyzer and BoundedFlowTracer were obtained via runtime downcast from the base ImpactAnalyzer/FlowTracer, but the sole concrete types (*impact.Analyzer, *flows.Tracer) and the test mock all implement the bounded method, so the downcast's ok was always true and the else fallback branches never executed. Fold the bounded method into the base interface, call it directly, and delete the two Bounded* interfaces, the downcasts, the dead else branches, and the now-unused mock method. No behavior change: the removed branches were unreachable in production. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent bb6d8b5 commit 471ec4c

3 files changed

Lines changed: 20 additions & 83 deletions

File tree

internal/mcp/deps.go

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,19 @@ type Parser interface {
3333
ParseWithContext(ctx context.Context, filePath string, content []byte) ([]model.Node, []model.Edge, error)
3434
}
3535

36-
// ImpactAnalyzer defines the blast-radius analysis contract for graph nodes.
37-
// @intent Injects an analyzer that calculates the impact radius of node changes into the server handler.
36+
// ImpactAnalyzer defines the bounded blast-radius analysis contract for graph nodes.
37+
// @intent inject a node/depth-capped blast-radius analyzer so a single MCP request cannot
38+
// expand into an unbounded graph walk.
3839
// @see mcp.handlers.getImpactRadius
3940
type ImpactAnalyzer interface {
40-
ImpactRadius(ctx context.Context, nodeID uint, depth int) ([]model.Node, error)
41-
}
42-
43-
// BoundedImpactAnalyzer extends ImpactAnalyzer with node and depth caps to prevent runaway traversal on large graphs.
44-
// @intent expose bounded blast-radius analysis for handlers that must protect shared MCP requests from unbounded graph walks.
45-
type BoundedImpactAnalyzer interface {
4641
ImpactRadiusBounded(ctx context.Context, nodeID uint, depth int, opts impactpkg.RadiusOptions) (*impactpkg.RadiusResult, error)
4742
}
4843

49-
// FlowTracer defines the call-flow tracing contract for graph nodes.
50-
// @intent Connects an analyzer to the server that reconstructs call flows starting from a given node.
44+
// FlowTracer defines the bounded call-flow tracing contract for graph nodes.
45+
// @intent inject a node-capped call-flow tracer so a deep call chain cannot expand into an
46+
// unbounded traversal.
5147
// @see mcp.handlers.traceFlow
5248
type FlowTracer interface {
53-
TraceFlow(ctx context.Context, startNodeID uint) (*model.Flow, error)
54-
}
55-
56-
// BoundedFlowTracer extends FlowTracer with a node cap to prevent runaway traversal on deeply nested call chains.
57-
// @intent let MCP handlers trace deep call chains without letting one request expand into an unbounded traversal.
58-
type BoundedFlowTracer interface {
5949
TraceFlowBounded(ctx context.Context, startNodeID uint, opts flowspkg.TraceOptions) (*flowspkg.TraceResult, error)
6050
}
6151

internal/mcp/handler_analysis.go

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -161,32 +161,13 @@ func (h *handlers) getImpactRadius(ctx context.Context, request mcp.CallToolRequ
161161
return "", nodeNotFoundErr(qn)
162162
}
163163

164-
var nodes []model.Node
165-
truncated := false
166-
if bounded, ok := h.deps.ImpactAnalyzer.(BoundedImpactAnalyzer); ok {
167-
res, err := bounded.ImpactRadiusBounded(ctx, node.ID, depth, impactpkg.RadiusOptions{MaxDepth: maxDepth, MaxNodes: maxNodes})
168-
if err != nil {
169-
log.ErrorContext(ctx, "impact analysis error", append(obs.TraceLogArgs(ctx), "node_id", node.ID, trace.SlogError(err))...)
170-
return "", trace.Wrap(err, "impact analysis error")
171-
}
172-
nodes = res.Nodes
173-
truncated = res.Truncated
174-
} else {
175-
if maxDepth > 0 && depth > maxDepth {
176-
depth = maxDepth
177-
truncated = true
178-
}
179-
var err error
180-
nodes, err = h.deps.ImpactAnalyzer.ImpactRadius(ctx, node.ID, depth)
181-
if err != nil {
182-
log.ErrorContext(ctx, "impact analysis error", append(obs.TraceLogArgs(ctx), "node_id", node.ID, trace.SlogError(err))...)
183-
return "", trace.Wrap(err, "impact analysis error")
184-
}
185-
if maxNodes > 0 && len(nodes) > maxNodes {
186-
nodes = nodes[:maxNodes]
187-
truncated = true
188-
}
164+
res, err := h.deps.ImpactAnalyzer.ImpactRadiusBounded(ctx, node.ID, depth, impactpkg.RadiusOptions{MaxDepth: maxDepth, MaxNodes: maxNodes})
165+
if err != nil {
166+
log.ErrorContext(ctx, "impact analysis error", append(obs.TraceLogArgs(ctx), "node_id", node.ID, trace.SlogError(err))...)
167+
return "", trace.Wrap(err, "impact analysis error")
189168
}
169+
nodes := res.Nodes
170+
truncated := res.Truncated
190171

191172
log.InfoContext(ctx, "get_impact_radius completed", append(obs.TraceLogArgs(ctx), "qualified_name", qn, "result_count", len(nodes))...)
192173

@@ -249,32 +230,15 @@ func (h *handlers) traceFlow(ctx context.Context, request mcp.CallToolRequest) (
249230
return "", nodeNotFoundErr(qn)
250231
}
251232

252-
var flow *model.Flow
253-
truncated := false
254-
containsFallbackCalls := false
255-
fallbackEdgesCount := 0
256-
if bounded, ok := h.deps.FlowTracer.(BoundedFlowTracer); ok {
257-
res, err := bounded.TraceFlowBounded(ctx, node.ID, flowspkg.TraceOptions{MaxNodes: maxNodes, IncludeFallbackCalls: &includeFallbackCalls})
258-
if err != nil {
259-
log.ErrorContext(ctx, "trace error", append(obs.TraceLogArgs(ctx), "node_id", node.ID, trace.SlogError(err))...)
260-
return "", trace.Wrap(err, "trace error")
261-
}
262-
flow = res.Flow
263-
truncated = res.Truncated
264-
containsFallbackCalls = res.ContainsFallbackCalls
265-
fallbackEdgesCount = res.FallbackEdgesCount
266-
} else {
267-
var err error
268-
flow, err = h.deps.FlowTracer.TraceFlow(ctx, node.ID)
269-
if err != nil {
270-
log.ErrorContext(ctx, "trace error", append(obs.TraceLogArgs(ctx), "node_id", node.ID, trace.SlogError(err))...)
271-
return "", trace.Wrap(err, "trace error")
272-
}
273-
if maxNodes > 0 && len(flow.Members) > maxNodes {
274-
flow.Members = flow.Members[:maxNodes]
275-
truncated = true
276-
}
233+
res, err := h.deps.FlowTracer.TraceFlowBounded(ctx, node.ID, flowspkg.TraceOptions{MaxNodes: maxNodes, IncludeFallbackCalls: &includeFallbackCalls})
234+
if err != nil {
235+
log.ErrorContext(ctx, "trace error", append(obs.TraceLogArgs(ctx), "node_id", node.ID, trace.SlogError(err))...)
236+
return "", trace.Wrap(err, "trace error")
277237
}
238+
flow := res.Flow
239+
truncated := res.Truncated
240+
containsFallbackCalls := res.ContainsFallbackCalls
241+
fallbackEdgesCount := res.FallbackEdgesCount
278242

279243
log.InfoContext(ctx, "trace_flow completed", append(obs.TraceLogArgs(ctx), "qualified_name", qn, "members", len(flow.Members))...)
280244

internal/mcp/testmocks_test.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -365,23 +365,6 @@ type mockFlowTracer struct {
365365
remainderErr error
366366
}
367367

368-
func (m *mockFlowTracer) TraceFlow(ctx context.Context, startNodeID uint) (*model.Flow, error) {
369-
m.calls++
370-
flow := m.returnFlow
371-
if flow == nil {
372-
flow = &model.Flow{
373-
Namespace: ctxns.FromContext(ctx),
374-
Name: "flow_from_mock",
375-
Members: []model.FlowMembership{{
376-
NodeID: startNodeID,
377-
Ordinal: 0,
378-
Namespace: ctxns.FromContext(ctx),
379-
}},
380-
}
381-
}
382-
return flow, m.traceErr
383-
}
384-
385368
func (m *mockFlowTracer) TraceFlowBounded(ctx context.Context, startNodeID uint, opts flows.TraceOptions) (*flows.TraceResult, error) {
386369
m.calls++
387370
m.opts = append(m.opts, opts)

0 commit comments

Comments
 (0)