mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
When a call resolved to a GraphQL client, emit_graphql_edge named the
Route after the whole first string argument, stripping only a leading
"query " or "mutation " (the mutation strip was also one byte short, so
names kept a leading space). An anonymous `query { ... }` therefore became
one long Route, and even a named operation carried its variables and
selection set in the name ("GetUser($id: ID!) { user(id: $id) { name } }").
Every distinct operation text minted its own Route node, and names were
cut off at 256 bytes.
Add cbm_service_pattern_graphql_operation(), a small GraphQL document
scanner. It skips comments, strings, block strings, `${...}` template
interpolations and leading fragment definitions, and reports the first
operation's type and name without copying any operation text. Routes are
now keyed as follows:
named operation __graphql__<Name> name <Name>
anonymous query / mutation / __graphql__<type>__anonymous name (anonymous <type>)
subscription, incl. the `{ ... }` query shorthand
no GraphQL document __graphql__operation__anonymous name (anonymous operation)
(URL, bare word, no string argument)
Keys are deterministic and bounded by the GraphQL Name grammar. The
GRAPHQL_CALLS edge keeps "operation" (now the Route name) and gains
"operation_type". The old fallback to the callee name ("client.query")
is gone: a callee is not an operation.
The sequential path (pass_calls.c) emits no GRAPHQL_CALLS at all, so it
had no copy of this logic to fix.
Proof on graphql-python/gql (374a70df, Python GraphQL client, index-only):
GraphQL Routes 170 -> 16, longest QN 266 -> 34 bytes, 106 -> 0 Routes
carrying operation text. GRAPHQL_CALLS 2643 -> 1158 with the same 984
distinct callers: edges are one per (caller, operation) pair, so calls
that used to hit distinct text-keyed Routes now share their operation's
Route.
Tests (edge_types_probe): graphql_route_keyed_by_operation_name_issue598
asserts the exact GraphQL Route set (QN + name) on the parallel path.
graphql_operation_identity_parser_issue598 covers braces inside comments,
strings and interpolations, fragments before the operation, anonymous
operations with variables, name truncation and non-document arguments.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
95 lines
4.8 KiB
C
95 lines
4.8 KiB
C
/*
|
||
* service_patterns.h — Allowlists for HTTP clients, async dispatch, and config accessors.
|
||
*
|
||
* Used during call resolution to classify CALLS edges as:
|
||
* HTTP_CALLS — synchronous HTTP client calls
|
||
* ASYNC_CALLS — async message/task dispatch
|
||
* CONFIGURES — config/env access
|
||
*
|
||
* Lookup is O(1) via hash table initialized once at startup.
|
||
*/
|
||
#ifndef CBM_SERVICE_PATTERNS_H
|
||
#define CBM_SERVICE_PATTERNS_H
|
||
|
||
#include <stdbool.h>
|
||
#include <stddef.h>
|
||
|
||
/* Edge type returned by pattern match. */
|
||
typedef enum {
|
||
CBM_SVC_NONE = 0, /* Not a service pattern — use normal CALLS */
|
||
CBM_SVC_HTTP = 1, /* Synchronous HTTP client call */
|
||
CBM_SVC_ASYNC = 2, /* Async dispatch (message broker, task queue) */
|
||
CBM_SVC_CONFIG = 3, /* Config/env accessor */
|
||
CBM_SVC_ROUTE_REG = 4, /* Route registration (router.GET, app.get, Route::post) */
|
||
CBM_SVC_GRPC = 5, /* gRPC client call (protobuf stub invocation) */
|
||
CBM_SVC_GRAPHQL = 6, /* GraphQL client query/mutation */
|
||
CBM_SVC_TRPC = 7, /* tRPC client procedure call */
|
||
} cbm_svc_kind_t;
|
||
|
||
/* Initialize the pattern lookup tables. Call once at startup. Thread-safe after init. */
|
||
void cbm_service_patterns_init(void);
|
||
|
||
/* Check if a resolved QN contains a known service library identifier.
|
||
* Returns the pattern kind, or CBM_SVC_NONE if no match.
|
||
* Matches on library name substrings in the QN (e.g., "requests" in
|
||
* "project.venv.requests.api.get"). Import-alias transparent. */
|
||
cbm_svc_kind_t cbm_service_pattern_match(const char *resolved_qn);
|
||
|
||
/* True for a bare, unqualified call to the native `fetch()` API. Deliberately
|
||
* NOT part of the substring tables above: those are matched unconditionally
|
||
* against the raw callee name before/regardless of registry resolution
|
||
* (the #523 external-library bypass), and "fetch" — unlike "axios" or
|
||
* "requests" — collides with a plausible local identifier. Callers must
|
||
* only consult this after registry resolution has come back empty, so a
|
||
* locally resolvable `function fetch(){}` / `const fetch = () => {}` is
|
||
* classified via its real resolved QN instead and never reaches this check. */
|
||
bool cbm_service_pattern_is_global_fetch(const char *callee_name);
|
||
|
||
/* True when a string literal is a plausible HTTP route for the given callee.
|
||
* Rejects filesystem paths and non-HTTP string consumers. */
|
||
bool cbm_service_pattern_is_http_route_literal(const char *literal, const char *callee_name);
|
||
|
||
/* True when text that reached a path slot is a source comment rather than a
|
||
* path: a block comment (slash-star … star-slash), a line comment (slash-slash
|
||
* followed by a space), or any text with a line break. The wildcard route
|
||
* (slash-star alone) is a path. */
|
||
bool cbm_service_pattern_is_comment_text(const char *text);
|
||
|
||
/* Per-worker TLS cache for cbm_service_pattern_match results. The
|
||
* pattern matcher runs once per resolved CALL edge in emit_service_
|
||
* edge — that's 6 pattern lists × ~30 patterns × strstr per call ≈
|
||
* ~180 strstrs per call. The same resolved QN repeats across most of
|
||
* the call edges in a project (e.g. "fmt.Errorf"), so caching turns
|
||
* a linear pattern-list scan into one hash lookup. Call _begin once
|
||
* per worker thread before the resolve loop and _end at the end. */
|
||
void cbm_service_pattern_cache_begin(void);
|
||
void cbm_service_pattern_cache_end(void);
|
||
|
||
/* Get the HTTP method from the callee name suffix (e.g., ".get" → "GET").
|
||
* Returns NULL if method cannot be inferred. */
|
||
const char *cbm_service_pattern_http_method(const char *callee_name);
|
||
|
||
/* Get the HTTP method from a route registration callee name suffix
|
||
* (e.g., "router.GET" → "GET", "app.post" → "POST").
|
||
* Returns NULL if not a known route registration method. */
|
||
const char *cbm_service_pattern_route_method(const char *callee_name);
|
||
|
||
/* Get the broker name for an async QN (e.g., "pubsub" from a Pub/Sub QN).
|
||
* Returns NULL if not an async pattern. */
|
||
const char *cbm_service_pattern_broker(const char *resolved_qn);
|
||
|
||
/* GraphQL operation identity of a client call argument (#598).
|
||
* Scans `doc` as a GraphQL document (skipping comments, strings, `${...}`
|
||
* template interpolations and leading fragment definitions) and reports the
|
||
* first operation: *op_type is "query", "mutation" or "subscription" ("query"
|
||
* for the `{ ... }` shorthand). Returns true and writes the operation name to
|
||
* name_buf when the operation is named. Returns false for an anonymous
|
||
* operation, or with *op_type = "operation" when `doc` is not a GraphQL
|
||
* document (NULL, a URL, a bare word such as a dict key ...). The operation
|
||
* text itself is never copied, so callers can key Routes on a bounded
|
||
* identity. */
|
||
bool cbm_service_pattern_graphql_operation(const char *doc, const char **op_type, char *name_buf,
|
||
size_t name_sz);
|
||
|
||
#endif /* CBM_SERVICE_PATTERNS_H */
|