Invoice
followed by object identity from the call that built it to the call that charged it.
The problem
Every tool on this site answers one question well. Sooner or later you’ll have one we didn’t think of.
Say Gateway#charge started timing out last week, and you want to know which checkout paths pass it
an Invoice that was built without a RateTable:
class Checkout
def complete(order)
invoice = order.invoice || Invoice.new(account: order.account)
Gateway.new.charge(invoice)
end
end
what_calls gives you the callers of charge. how_it_got_here gives you the chains that reach it.
Neither joins those chains to how the argument was built, filtered to the last seven days of
production, grouped by entry point. You could export the recording and write a script. Or you could
ask the graph.
The fix
Every recorded call is already a node and an edge in the hosted graph. Write the question in Cypher:
MATCH (entry:EntryPoint)-[:ROOT_OF]->(:Call)-[:CALLS*]->(charge:Call {method: 'Gateway#charge'})
MATCH (charge)-[:ARG]->(invoice:Object {class: 'Invoice'})
MATCH (built:Call {method: 'Invoice.new'})-[:RETURNED]->(invoice)
WHERE charge.deployed_at > datetime() - duration('P7D')
AND NOT (built)-[:ARG {name: 'rate_table'}]->()
RETURN entry.identity AS entry_point, count(*) AS calls
ORDER BY calls DESC
$ ra cypher -f slow_charges.cypher
entry_point calls
POST /checkout (CheckoutsController) 1,904
RenewalJob#perform 212
Two entry points build an Invoice without a rate table, and the renewal job is new this week. That’s
the one to fix:
class RenewalJob
def perform(subscription)
invoice = Invoice.new(account: subscription.account, rate_table: RateTable.for(subscription.account))
Checkout.new.complete(subscription.order_with(invoice))
end
end
Your agent can do the same over MCP: it writes the query, runs it, and reads the rows back.
How it works
The hosted graph is a Neo4j database that every recorded deploy writes into. The schema is small:
Callnodes, one per recorded call, with themethod, the file and line, and the deploy.CALLSedges from a call to the calls it made, in order, with counts on the rolled-up edges.Objectnodes for the receivers, arguments and return values, labelled by class, joined by object identity so a value can be followed from the call that built it to every call it reached.EntryPointnodes for the spec example, request or job that started each call tree.
That’s the same structure every other tool queries. cypher hands you the query language directly,
read-only, over the pooled graph for your whole app.
Limits
- Classes and names, never values. The graph stores class names, method names and object identity. You can ask which class an argument was; you can’t ask what it contained.
- Read-only. Queries can’t change the graph.
- It’s a real query language. A query that walks every path in a busy app will be slow. Queries run with a time limit, and anything you run often is a candidate for a tool of its own.
Related tools
coupling_map
Coupling from real load. Module and namespace coupling drawn from real production calls. Which parts of your app actually talk, and how much.
See more →what_calls
Who can call this method. Every caller of a method from real runs, with where and how often. The calls that actually happened, not grep. what_it_calls does the reverse.
See more →where_nil_comes_from
Where nil shows up. The methods that returned nil and the arguments that received it at runtime, logged as NilClass and tied to the errors that followed. The case a declared type quietly hides.
See more →