Skip to content

Commit 1b79373

Browse files
feat(schema): introduce Producer node and route ASYNC_CALLS through it
Add Producer/DECLARES_PRODUCER, flip ASYNC_CALLS to Producer→Route, wire GraphMeta counters, kuzu two-hop async queries, MCP find/resolve producer parity, describe rollups, and PR-C tests (ontology 14 unchanged). Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 51c1409 commit 1b79373

20 files changed

Lines changed: 650 additions & 84 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ Unresolved targets become **phantom** nodes (`resolved=false`, FQN guessed from
386386
| `CALLS` | method → method | In-process call (confidence-scored, strategy-tagged). |
387387
| `EXPOSES` | type → route | Type exposes an HTTP/async route. |
388388
| `HTTP_CALLS` | client → route | Cross-service HTTP call (caller-side Client to target Route). |
389-
| `ASYNC_CALLS` | symbol → route | Cross-service async (Kafka, Rabbit, JMS, …). |
389+
| `ASYNC_CALLS` | producer → route | Cross-service async (Kafka, Rabbit, JMS, …). |
390390

391391
JDK / Spring / Lombok callees are represented as **phantom** method symbols at index time. Caller/callee traversals default to `exclude_external=true` so those edges are filtered by FQN prefix without dropping them from the graph.
392392

@@ -426,7 +426,7 @@ Resolution order for `microservice`:
426426

427427
Current ontology version is **14**. Any index built before this version must be rebuilt via `cocoindex update ... --full-reprocess -f` or a full `java-codebase-rag reprocess` (no selective flags) so vectors and graph stay aligned. Until re-indexed, the server defensively JSON-decodes string-form list columns so nothing explodes, but filters like `array_contains` will not work.
428428

429-
Ontology **14** introduces `EDGE_SCHEMA` in `java_ontology.py` as the canonical edge navigation schema (see `docs/EDGE-NAVIGATION.md`). **`HTTP_CALLS` is `Client → Route`** (SCHEMA-V2 PR-B). **`ASYNC_CALLS` remains `Symbol → Route` until PR-C**, which adds the `Producer` node, `DECLARES_PRODUCER`, and flips `ASYNC_CALLS` to `Producer → Route`. Run one full reprocess after upgrading through the SCHEMA-V2 sequence (or when you need the v14 ontology gate).
429+
Ontology **14** introduces `EDGE_SCHEMA` in `java_ontology.py` as the canonical edge navigation schema (see `docs/EDGE-NAVIGATION.md`). **`HTTP_CALLS` is `Client → Route`** (SCHEMA-V2 PR-B). **`ASYNC_CALLS` is `Producer → Route`** with `DECLARES_PRODUCER` (SCHEMA-V2 PR-C). Run one full reprocess after upgrading through the SCHEMA-V2 sequence (or when you need the v14 ontology gate).
430430

431431
Ontology **13** materializes stored `OVERRIDES` edges between method Symbols (subtype override → supertype declaration, matching `signature` on a direct `IMPLEMENTS` / `EXTENDS` hop). `neighbors(edge_types=["OVERRIDES"])` traverses this relationship; `OVERRIDDEN_BY*` keys in `edge_summary` remain describe-time rollups only.
432432

build_ast_graph.py

Lines changed: 169 additions & 9 deletions
Large diffs are not rendered by default.

docs/AGENT-GUIDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Use these strings **verbatim** in `neighbors(..., edge_types=[...])`:
9696
| Method overrides | `OVERRIDES` | Subtype **method** → supertype **declaration** method (same `signature`, one `IMPLEMENTS`/`EXTENDS` hop). `in` = overriders; `out` = overridden declarations |
9797
| Method calls | `CALLS` | `in` = callers; `out` = callees |
9898
| Service boundary | `EXPOSES` | Symbol → Route (handler exposes route) |
99-
| Cross-service | `HTTP_CALLS`, `ASYNC_CALLS` | `HTTP_CALLS`: Client → Route; `ASYNC_CALLS`: Symbol → Route until SCHEMA-V2 PR-C (`Producer` node) |
99+
| Cross-service | `HTTP_CALLS`, `ASYNC_CALLS` | `HTTP_CALLS`: Client → Route; `ASYNC_CALLS`: Producer → Route via `DECLARES_PRODUCER` |
100100

101101
Symmetric: cross-service and intra-service questions use the **same** `neighbors` call with different `edge_types`.
102102

@@ -191,6 +191,7 @@ Exact allowed values for roles, capabilities, client kinds, etc. live in `java_o
191191
| List interfaces in service S | `find(kind="symbol", filter={"microservice":S,"symbol_kind":"interface"})` | `neighbors` / `describe` |
192192
| List HTTP or Kafka entry points | `find(kind="route", filter={...})` | `describe` |
193193
| List Feign / HTTP clients | `find(kind="client", filter={...})` | `neighbors(..., out, ["HTTP_CALLS"])` if needed |
194+
| List async producers | `find(kind="producer", filter={...})` | `neighbors(..., out, ["ASYNC_CALLS"])` if needed |
194195
| Who calls method M? | Stable symbol id via `resolve`, `find`, or `search` | `neighbors(ids=sym_id, direction="in", edge_types=["CALLS"])` |
195196
| What does M call? | Same | `neighbors(..., direction="out", edge_types=["CALLS"])` |
196197
| Who hits this route? | `find(kind="route", ...)` or route id from logs | `neighbors(ids=route_id, direction="in", edge_types=["HTTP_CALLS","ASYNC_CALLS","EXPOSES"])` |
@@ -263,7 +264,7 @@ Virtual keys (`OVERRIDDEN_BY`, …) and composed dot-keys are **not** valid `Edg
263264

264265
### Ontology glossary (version 14)
265266

266-
Source of truth: `java_ontology.py` (`EDGE_SCHEMA`, valid sets). Strings are case-sensitive. Edge navigation: [`docs/EDGE-NAVIGATION.md`](./EDGE-NAVIGATION.md) — for `HTTP_CALLS`, traverse via `DECLARES_CLIENT` from a method Symbol or `neighbors` outbound from a Client id; `ASYNC_CALLS` still uses `*_current` member traversals until SCHEMA-V2 PR-C.
267+
Source of truth: `java_ontology.py` (`EDGE_SCHEMA`, valid sets). Strings are case-sensitive. Edge navigation: [`docs/EDGE-NAVIGATION.md`](./EDGE-NAVIGATION.md) — for `HTTP_CALLS`, traverse via `DECLARES_CLIENT` from a method Symbol or `neighbors` outbound from a Client id; for `ASYNC_CALLS`, traverse via `DECLARES_PRODUCER` or outbound from a Producer id.
267268

268269
**Roles:** `CONTROLLER`, `SERVICE`, `REPOSITORY`, `COMPONENT`, `CONFIG`, `ENTITY`, `CLIENT`, `MAPPER`, `DTO`, `OTHER`.
269270

docs/EDGE-NAVIGATION.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
| CALLS | Symbol | Symbol | many_to_many | yes | yes |
1616
| EXPOSES | Symbol | Route | one_to_one | yes | yes |
1717
| DECLARES_CLIENT | Symbol | Client | one_to_many | yes | yes |
18+
| DECLARES_PRODUCER | Symbol | Producer | one_to_many | yes | yes |
1819
| HTTP_CALLS | Client | Route | many_to_many | yes | no |
19-
| ASYNC_CALLS | Symbol | Route | many_to_many | yes | no |
20+
| ASYNC_CALLS | Producer | Route | many_to_many | yes | no |
2021

2122
## EXTENDS
2223

@@ -183,6 +184,26 @@
183184
- `member_subject`: neighbors(['{id}'],'out',['DECLARES_CLIENT'])
184185
- `alien_subject`: DECLARES_CLIENT connects method Symbol → Client
185186

187+
## DECLARES_PRODUCER
188+
189+
**Endpoints**: `Symbol → Producer`
190+
**Cardinality**: `one_to_many`
191+
**Brownfield-resolver-sourced**: yes
192+
**Member-only** (hints): yes
193+
194+
**Purpose**: method declares an outbound async producer call site
195+
196+
**Attributes**:
197+
198+
- `confidence` (`DOUBLE`) — producer declaration confidence in [0.0, 1.0]
199+
- `strategy` (`STRING`) — producer resolution strategy literal
200+
201+
**Typical traversals**:
202+
203+
- `type_subject`: neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'{direction}',['DECLARES_PRODUCER'])
204+
- `member_subject`: neighbors(['{id}'],'out',['DECLARES_PRODUCER'])
205+
- `alien_subject`: DECLARES_PRODUCER connects method Symbol → Producer
206+
186207
## HTTP_CALLS
187208

188209
**Endpoints**: `Client → Route`
@@ -209,12 +230,12 @@
209230

210231
## ASYNC_CALLS
211232

212-
**Endpoints**: `Symbol → Route`
233+
**Endpoints**: `Producer → Route`
213234
**Cardinality**: `many_to_many`
214235
**Brownfield-resolver-sourced**: yes
215236
**Member-only** (hints): no
216237

217-
**Purpose**: resolved async call from declaring method to topic route (pre-flip: Symbol→Route; PR-C: Producer→Route)
238+
**Purpose**: resolved async call from a declared Producer to a topic route
218239

219240
**Attributes**:
220241

@@ -226,8 +247,7 @@
226247

227248
**Typical traversals**:
228249

229-
- `type_subject_current`: neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'out',['ASYNC_CALLS'])
230250
- `type_subject`: neighbors(['{id}'],'out',['DECLARES']) then neighbors(member_ids,'out',['DECLARES_PRODUCER']) then neighbors(producer_ids,'out',['ASYNC_CALLS'])
231-
- `member_subject_current`: neighbors(['{id}'],'out',['ASYNC_CALLS'])
232251
- `member_subject`: neighbors(['{id}'],'out',['DECLARES_PRODUCER']) then neighbors(producer_ids,'out',['ASYNC_CALLS'])
233-
- `alien_subject`: ASYNC_CALLS is Symbol→Route until PR-C; use member_subject_current. After PR-C (Producer→Route), use member_subject via DECLARES_PRODUCER
252+
- `route_subject`: neighbors(['{id}'],'in',['ASYNC_CALLS']) then neighbors(producer_ids,'in',['DECLARES_PRODUCER']) for declaring method
253+
- `alien_subject`: ASYNC_CALLS connects Producer→Route; use DECLARES_PRODUCER from a method Symbol, or neighbors(producer_id,'out',['ASYNC_CALLS']) from a Producer id

docs/skills/java-codebase-explore.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ You cannot reason reliably about cross-service behaviour until these surfaces ex
9898
**Sequence:**
9999

100100
1. Cluster routes by path prefix; **`describe`** on representative `route:` ids.
101-
2. For each major route, **`neighbors(direction="in", edge_types=["EXPOSES"])`** to land on handler symbols; for inbound **`HTTP_CALLS`**, expect **Client** callers (then **`DECLARES_CLIENT` inbound** to the declaring method); **`ASYNC_CALLS`** inbound still lands on Symbol callers until PR-C.
102-
3. Use **`find(kind="client", …)`** with the same microservice filter to list outbound integration points; follow outbound **`HTTP_CALLS`** from each Client (or **`ASYNC_CALLS`** from methods until Producer lands).
101+
2. For each major route, **`neighbors(direction="in", edge_types=["EXPOSES"])`** to land on handler symbols; for inbound **`HTTP_CALLS`**, expect **Client** callers (then **`DECLARES_CLIENT` inbound** to the declaring method); for inbound **`ASYNC_CALLS`**, expect **Producer** callers (then **`DECLARES_PRODUCER` inbound** to the declaring method).
102+
3. Use **`find(kind="client", …)`** and **`find(kind="producer", …)`** with the same microservice filter to list outbound integration points; follow outbound **`HTTP_CALLS`** from each Client and **`ASYNC_CALLS`** from each Producer.
103103

104104
**Stopping rule:** You can summarize how traffic enters the service, what modules/controllers own key paths, and what external systems it calls—**without** claiming tests, runtime config, or unindexed siblings exist in MCP.
105105

@@ -116,7 +116,7 @@ You cannot reason reliably about cross-service behaviour until these surfaces ex
116116
**Sequence:**
117117

118118
1. **`neighbors(direction="in", edge_types=["EXPOSES"])`** onto the handling symbol; walk **`CALLS`** outbound method-by-method.
119-
2. When a method makes outbound HTTP, **`neighbors(..., out, ["DECLARES_CLIENT"])`** then outbound **`HTTP_CALLS`** from each Client id; for async (pre-PR-C), **`ASYNC_CALLS`** may still be direct from the method Symbol.
119+
2. When a method makes outbound HTTP, **`neighbors(..., out, ["DECLARES_CLIENT"])`** then outbound **`HTTP_CALLS`** from each Client id; for async, **`neighbors(..., out, ["DECLARES_PRODUCER"])`** then outbound **`ASYNC_CALLS`** from each Producer id.
120120
3. Stop at leaves, framework boundaries, or unresolved edges; read **`edge.attrs`** (`attrs.confidence`, `attrs.strategy`, `attrs.match`) and report low-confidence segments as resolver gaps, not as facts.
121121

122122
**Stopping rule:** You reach a stable leaf (external IO, message publish, clear terminal layer) **or** you document every unresolved hop with a concrete next non-MCP check.
@@ -239,11 +239,11 @@ Ten edge types:
239239
| Group | Edges |
240240
| ----- | ----- |
241241
| Type wiring | `EXTENDS`, `IMPLEMENTS`, `INJECTS` |
242-
| Containment | `DECLARES`, `DECLARES_CLIENT` |
242+
| Containment | `DECLARES`, `DECLARES_CLIENT`, `DECLARES_PRODUCER` |
243243
| Method overrides | `OVERRIDES` |
244244
| Method calls | `CALLS` |
245245
| Service boundary | `EXPOSES` |
246-
| Cross-service | `HTTP_CALLS` (Client→Route), `ASYNC_CALLS` (Symbol→Route until Producer) |
246+
| Cross-service | `HTTP_CALLS` (Client→Route), `ASYNC_CALLS` (Producer→Route) |
247247

248248
For exact argument shapes, recovery playbook, and slash aliases see
249249
[`docs/AGENT-GUIDE.md`](https://github.com/HumanBean17/java-codebase-rag/blob/master/docs/AGENT-GUIDE.md) in the java-codebase-rag repo.

java_ontology.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,26 @@ class EdgeSpec:
301301
"alien_subject": "DECLARES_CLIENT connects method Symbol → Client",
302302
},
303303
),
304+
"DECLARES_PRODUCER": EdgeSpec(
305+
name="DECLARES_PRODUCER",
306+
src="Symbol",
307+
dst="Producer",
308+
cardinality="one_to_many",
309+
brownfield_resolver_sourced=True,
310+
attrs=(
311+
EdgeAttr("confidence", "DOUBLE", "producer declaration confidence in [0.0, 1.0]"),
312+
EdgeAttr("strategy", "STRING", "producer resolution strategy literal"),
313+
),
314+
purpose="method declares an outbound async producer call site",
315+
member_only=True,
316+
typical_traversals={
317+
"type_subject": _SYMBOL_TYPE_TRAVERSAL.format(
318+
id="{id}", direction="{direction}", edge="DECLARES_PRODUCER",
319+
),
320+
"member_subject": "neighbors(['{id}'],'out',['DECLARES_PRODUCER'])",
321+
"alien_subject": "DECLARES_PRODUCER connects method Symbol → Producer",
322+
},
323+
),
304324
"HTTP_CALLS": EdgeSpec(
305325
name="HTTP_CALLS",
306326
src="Client",
@@ -337,7 +357,7 @@ class EdgeSpec:
337357
),
338358
"ASYNC_CALLS": EdgeSpec(
339359
name="ASYNC_CALLS",
340-
src="Symbol",
360+
src="Producer",
341361
dst="Route",
342362
cardinality="many_to_many",
343363
brownfield_resolver_sourced=True,
@@ -348,25 +368,24 @@ class EdgeSpec:
348368
EdgeAttr("raw_topic", "STRING", "uninterpolated topic template from the call site"),
349369
EdgeAttr("match", "STRING", "cross_service|intra_service|ambiguous|phantom|unresolved"),
350370
),
351-
purpose="resolved async call from declaring method to topic route (pre-flip: Symbol→Route; PR-C: Producer→Route)",
371+
purpose="resolved async call from a declared Producer to a topic route",
352372
typical_traversals={
353-
"type_subject_current": (
354-
"neighbors(['{id}'],'out',['DECLARES']) "
355-
"then neighbors(member_ids,'out',['ASYNC_CALLS'])"
356-
),
357373
"type_subject": (
358374
"neighbors(['{id}'],'out',['DECLARES']) "
359375
"then neighbors(member_ids,'out',['DECLARES_PRODUCER']) "
360376
"then neighbors(producer_ids,'out',['ASYNC_CALLS'])"
361377
),
362-
"member_subject_current": "neighbors(['{id}'],'out',['ASYNC_CALLS'])",
363378
"member_subject": (
364379
"neighbors(['{id}'],'out',['DECLARES_PRODUCER']) "
365380
"then neighbors(producer_ids,'out',['ASYNC_CALLS'])"
366381
),
382+
"route_subject": (
383+
"neighbors(['{id}'],'in',['ASYNC_CALLS']) "
384+
"then neighbors(producer_ids,'in',['DECLARES_PRODUCER']) for declaring method"
385+
),
367386
"alien_subject": (
368-
"ASYNC_CALLS is Symbol→Route until PR-C; use member_subject_current. "
369-
"After PR-C (Producer→Route), use member_subject via DECLARES_PRODUCER"
387+
"ASYNC_CALLS connects Producer→Route; use DECLARES_PRODUCER from a method Symbol, "
388+
"or neighbors(producer_id,'out',['ASYNC_CALLS']) from a Producer id"
370389
),
371390
},
372391
),

0 commit comments

Comments
 (0)