Skip to content

RemoveRoutes RPC performs a lookup instead of deleting routes #352

Description

@MicroMilo

Summary

The public interface, implementation, and protobuf define RemoveRoutes as a deletion returning Empty, but the gRPC handler calls findRoutes and returns a routes-shaped object. The actual deletion method is never invoked.

Impact: A successful RemoveRoutes RPC leaves the AOR's routes in the store. Subsequent lookups can continue routing to an endpoint the caller believed was deregistered.

Code path

Checked against current main at commit bf3de25a401d2313538677486400864354ff5edf.

  • mods/location/src/utils.ts:79-87
  • mods/location/src/location.ts:138-140
  • mods/common/src/protos/location.proto:32-35

Steps to reproduce

Validation level: current HEAD supplemental dynamic witness reproduced.

node -e 'const fs=require("fs"),source=fs.readFileSync("mods/location/src/utils.ts","utf8"); function compile(name,locator){const marker=name+": async (call: CT.GrpcCall, callback) => ",start=source.indexOf(marker)+marker.length; let depth=0,end=-1; for(let i=start;i<source.length;i++){if(source[i]==="{")depth++; if(source[i]==="}"&&--depth===0){end=i;break}} const body=source.slice(start,end+1).replace("call.request as FindRoutesRequest","call.request"); return new Function("locator","return async (call, callback) => "+body)(locator)} let findCalls=0,removeCalls=0,callbackResult; const locator={findRoutes:async()=>{findCalls++;return[{host:"still-registered"}]},removeRoutes:async()=>{removeCalls++}}; compile("removeRoutes",locator)({request:{aor:"sip:alice@example.test"}},(err,value)=>{callbackResult={err:err??null,value}}).then(()=>console.log(JSON.stringify({findCalls,removeCalls,callbackResult})))'

Observed output:

{"findCalls":1,"removeCalls":0,"callbackResult":{"err":null,"value":{"routes":[{"host":"still-registered"}]}}}

Neighboring control:

node -e 'const fs=require("fs"),source=fs.readFileSync("mods/location/src/utils.ts","utf8"); function compile(name,locator){const marker=name+": async (call: CT.GrpcCall, callback) => ",start=source.indexOf(marker)+marker.length; let depth=0,end=-1; for(let i=start;i<source.length;i++){if(source[i]==="{")depth++; if(source[i]==="}"&&--depth===0){end=i;break}} const body=source.slice(start,end+1).replace(/call\.request as \w+/g,"call.request"); return new Function("locator","return async (call, callback) => "+body)(locator)} let addCalls=0,findCalls=0,callbackResult; const locator={addRoute:async()=>{addCalls++},findRoutes:async()=>{findCalls++;return[]}}; compile("addRoute",locator)({request:{aor:"sip:alice@example.test"}},(err,value)=>{callbackResult={err:err??null,value}}).then(()=>console.log(JSON.stringify({addCalls,findCalls,callbackResult})))'

Control output:

{"addCalls":1,"findCalls":0,"callbackResult":{"err":null,"value":{}}}

Expected behavior

The documented/source-grounded contract should hold without the drift described above.

Actual behavior

The RemoveRoutes handler performs findRoutes, never invokes deletion, and returns route data despite the protobuf Empty contract.

Existing coverage

I checked the current issue and PR lists for overlapping titles/root-cause keywords before filing this. I did not find an item covering this same root cause.

Suggested fix

Dispatch the RemoveRoutes gRPC handler to locator.removeRoutes and return an Empty response.

Suggested tests

  • Add a regression test for the reproduction above.
  • Add a neighboring control assertion so the intended non-bug path remains covered.

Submitted with Codex.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions