fix(query): make agent query errors actionable
What does this MR do and why?
When an agent sent a bad query, the Orbit error did not tell it how to fix the query. The agent guessed, and it sent the same mistake again. This MR makes syntax and schema errors actionable. The parser names an unsupported function such as type(), and it names clauses instead of grammar rules. The schema command rejects unknown node names. The traversal_path scope loop is fixed in the next MR in the stack, which hides that field from clients.
Related Issues
Relates to #1377. Companion MRs: gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!7405 (Duo Chat card) and gitlab-org/gitlab!261313 (merged) (MCP client errors).
Testing
I tested end to end with Duo Chat on a GDK VM. The VM ran this MR, the ai-assist MR, and the gitlab MR together.
| Before (gitlab.com) | After (GDK) |
|---|---|
![]() |
![]() |
After this change, the agent proposed the correct fix for each of the four bad calls.
Performance Analysis
- This merge request does not introduce any performance regression. If a performance regression is expected, explain why.
The new code runs only on error paths.
Agent context: long-form analysis, file-by-file walkthroughs, profiler output, alternatives considered
How it works
-
A
FunctionCallgrammar rule matches anyname(args)inRETURN,ORDER BY, and both sides of aWHEREcomparison. The syntax tree consumer always rejects it, likeLegacyShortestPath. The error names the function, which is an ASCII identifier and never a literal, and lists the supported functions. Fortype(r), it points to typed relationships. See query.pest#L29-33 and syntax.rs#L605-618.FunctionCall = { FunctionName ~ "(" ~ SP? ~ (FunctionArgument ~ (SP? ~ "," ~ SP? ~ FunctionArgument)*)? ~ SP? ~ ")" } FunctionName = @{ !ReservedWord ~ UnescapedSymbolicName } -
pest rule names in syntax errors map to clause names through
renamed_rules, soexpected EOI, Order, Limit, Page, or Debugbecomesexpected end of query, ORDER BY, LIMIT, PAGE, or DEBUG. -
get_graph_schemarejectsexpand_nodesnames that are not in the ontology and lists the valid names.*still expands every node. The REST schema endpoint uses a different RPC, so it does not change. See service.rs#L244-273.
Error text before and after
| Call | Before | After |
|---|---|---|
RETURN m.id, type(r) |
... column 74: expected EOI |
function type() is not supported: match a typed relationship such as -[r:CLOSES]->; each edge type is listed under @edges in the response |
RETURN u LIMIT(1) |
expected EOI, Order, Limit, Page, or Debug |
expected end of query, ORDER BY, LIMIT, PAGE, or DEBUG |
expand_nodes: ["NoSuchNode"] |
full schema, no error | Invalid arguments: unknown node(s) in expand_nodes: NoSuchNode. Valid nodes: ... |
Tests
- New scenarios
edge_cases/gql_unsupported_function.yaml,gql_unsupported_function_in_where.yaml(also checks that the literal is not echoed),gql_supported_function_with_unsupported_arguments.yaml, andgql_syntax_error_names_clauses.yaml. graph_schema_ignores_unknown_expansion_nodesbecamegraph_schema_rejects_unknown_expansion_nodes.- Local runs:
compiler353 passed,integration-tests --test local225 passed,integration-testkit135 passed,orbit-server graph_schema10 passed. Both GQL fuzz targets pass. Clippy reported 0 warnings. - The literal check passed: errors never echo
'private-query-literal'. - Follow-up:
type(r)support (WHERE type(r) = 'X'lowers toedge.types;RETURN type(r)as a no-op), then aggregation group bytype(r)with security scenarios.


