You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(execution-factory): add a Cypher tool type usable as an object-type logic property source #2670
Setup. Verification environment, network supply_ontology_hand (the bkn-samples supply chain sample: suppliers, purchase orders, purchase requests, MRP, materials, inventory, products, BOM, sales orders). One function toolbox, 29 functions attempted (14 saved, 15 meant to be refused), 45 calls through /debug and /proxy. Statements below drop the supply_ontology_hand_ prefix from object and relation type names.
Result. Saving, calling, editing, timeouts and type checks behave as documented. Ten problems came up. The first is a wrong result returned without an error; the next five are statements a function author will naturally write that the Cypher subset refuses. Most of them sit in bkn-backend's Cypher compiler, not in execution-factory.
Problems
1. A date property compared with a string that is not a date matches everything, with no error.
MATCH (o:salesorder) WHEREo.signing_date>= $sinceRETURNcount(o)
The unparsable value behaves like the earliest possible time. A function is called by an LLM or a script, so a value like 2026年4月20日 is a realistic input, and the caller gets a plausible but wrong count. Wrapping the parameter as datetime($since) or date($since) already rejects the same values with a clear 400; only the bare comparison skips the check. The save-time explain cannot catch this, because it runs with the valid example value.
Suggested: validate a string compared with a date or datetime property the same way datetime() does. Until then, the docs and the cypher_generation prompt should write datetime($x).
2. IN $list with an empty list fails the call. WHERE m.material_code IN $codes with codes: [] returns status 400: "IN over nothing matches nothing, so leave the condition out instead". A function author cannot leave the condition out, and the caller expects an empty table (or null / 0 for a value function). Suggested: treat an empty list as matching nothing and return no rows.
3. LIMIT $n and SKIP $n are refused at save time.
"LIMIT takes an integer literal". A "top N" or paged function needs a separate function for each N. Suggested: accept an integer parameter, checked against the 10000 cap at call time.
4. Optional filters cannot be written. WHERE ($code IS NULL OR p.supplier_number = $code) is refused ("IN and IS NULL apply to variable.property"), and so is ($only_open = false OR p.rowclosestatus_title <> '已关闭') ("a comparison must read a property"). Every input of a Cypher function is also required. Together this means one function per combination of filters, and a boolean input can only be compared with a property.
5. Arithmetic across two WITH stages is refused with a misleading message.
MATCH (m:material{material_code:'725-000334'})-[:mat2inv]->(i:inventory)
WITHm, sum(i.available_inventory_qty) ASavailWITHm.purchase_fixedleadtimeASlt, availRETURNlt-availASd
Refused: "d" reads purchase_fixedleadtime outside an aggregate. Projecting lt in the first WITH works, and so does returning lt, avail without the subtraction, so the statement is valid. This blocks the natural shortage query: sales order → product → BOM → material, then inventory, then need - avail.
6. Function support is narrow, and the refusal does not say which function. round(), toUpper() and size() all come back as "function calls is not supported". coalesce(), CASE, date() and datetime() work. Also refused: collect() next to sum() ("run the count as a query of its own"), and list slicing collect(x)[0..3]. Suggested: name the function in the message and list the supported ones in the Cypher manual.
7. Numbers come back in two forms. sum() of a decimal property returns the string "13833141.000000", while sum(a) * 100.0 / sum(b) returns the float 72.8975147437592. The value function's output schema leaves the type open ("A string, a number, a boolean or null"). A consumer that compares values or does arithmetic has to handle both forms. Keeping decimals as strings protects precision, so this is mainly a documentation gap: the call guide and the output schema should state it.
8. Multi-hop queries come close to the 55 s statement limit.
Shortage for one sales order (4 hops plus an OPTIONAL MATCH on inventory, 507 materials): 39 s.
BOM size per product over all products (3 hops): 20 s with a STARTS WITH $prefix filter, and timed out at 55 s without one.
The caller timeout works (timeout: 3 and 5 give 504 on time), but a caller using the default timeout waits close to a minute before it fails.
9. Error details mix two languages and wrap twice.
Example: the cypher statement was refused: Cypher 查询使用了本接口不支持的语法。请改写为受支持的只读子集。: line 1:213: .... The useful part is the last clause; the Chinese sentence in the middle repeats bkn-backend's description.
10. Editing without script_type names the wrong cause.
An edit that omits script_type gets "a cypher function cannot become a python function; create a new function instead". This is documented (the field defaults to python), but the message reads as if the caller asked to switch languages. Suggested: default to the stored type, or say that script_type: cypher is missing.
What passed
45 calls, grouped
Group
Case
Result
value
supplier name by code; unknown code; x' OR '1'='1
name; null; null (treated as a value)
value
PO lines per supplier over po2sup
240, matches a direct count
value
decimal sum, decimal property, property that is null
"13833141.000000", "839.999964", null
value
count of sales orders by status
784 / 16, matches the data
table
240 PO lines, no LIMIT
240 rows, limited: false
table
5645 BOM lines, no LIMIT
1000 rows, limited: true
table
IN $codes with two codes; a string instead of a list
2 rows; 400 "must be a list"
table
>= $since with 2026-04-20 and with 2026-04-20T00:00:00+08:00
input not in statement, $name with no input, input without description
400 naming the parameter
refused at save
value shape with 2 columns, value shape returning a node
400 suggesting the table shape
edit
change statement; to a write; to 2 columns (value); new $nm without input; to python
saved and new result returned; the rest refused
Proxy calls also need the toolbox published and the tool enabled; new functions start disabled. /debug needs neither. That is the same for Python functions.
Not covered
A non-admin caller with row or column permissions.
Calls through Context Loader execute_tool and through a logic property binding.
Studio UI. All of the above went through the HTTP API.
Problems 1–6 are in bkn-backend's Cypher compiler, problems 9–10 in execution-factory, and 7–8 need docs more than code. I can open separate issues if that is easier to track.
fix(bkn-backend): refuse non-date strings, accept empty IN lists in Cypher #3075: problems 1, 2, 5, 6 (bkn-backend). A string compared with a date property has to be a date; IN $list with [] matches nothing; arithmetic over a property of a node a WITH grouped by; refused functions are named. Also execution-factory's explain placeholder for a string input becomes 2000-01-01, so the date check does not refuse functions saved without examples.
All three were run against supply_ontology_hand on the verification environment beside the deployed services. Suggested merge order: #3075, #3076, #3077.
Not addressed here: problem 4 (optional filters) needs its own design; new functions such as round() (problem 6) are a feature; problems 7 and 8 are documentation.
// Optional filters: pass status / since, or leave them outMATCH (o:salesorder)
WHERE ($statusISNULLORo.order_status= $status)
AND ($sinceISNULLORo.signing_date>= $since)
RETURNcount(o) ASn// A rounded ratio, returned as a numberMATCH (p:po)-[:po2sup]->(s:supplier{supplier_code: $code})
RETURNround(sum(p.actqty) *100.0/sum(p.qty), 2) ASreceived_pct
On the VM, the first statement, saved as a function with two required: false inputs, returns 4, 16, 800 and 800 for both inputs given, one given, none, and both null. The second returns 72.9 for supplier 59202002.
Activity
sh00tg0a1 commentedon Nov 10, 2026
PRs are open:
Both were verified on the dev VM. Not yet covered: a non-admin caller with row-level permissions.
feat(execution-factory): add Cypher functions that run one read-only …
feat(execution-factory): write, try and publish Cypher functions (#1293)
fix(execution-factory): keep a Cypher function read-only for readers …
test(execution-factory): cover a Cypher function's export and import …
sh00tg0a1 commentedon Nov 11, 2026
Verification on the supply chain sample
Setup. Verification environment, network
supply_ontology_hand(the bkn-samples supply chain sample: suppliers, purchase orders, purchase requests, MRP, materials, inventory, products, BOM, sales orders). One function toolbox, 29 functions attempted (14 saved, 15 meant to be refused), 45 calls through/debugand/proxy. Statements below drop thesupply_ontology_hand_prefix from object and relation type names.Result. Saving, calling, editing, timeouts and type checks behave as documented. Ten problems came up. The first is a wrong result returned without an error; the next five are statements a function author will naturally write that the Cypher subset refuses. Most of them sit in bkn-backend's Cypher compiler, not in execution-factory.
Problems
1. A date property compared with a string that is not a date matches everything, with no error.
$since2026-04-20not-a-date,2026年4月20日,下周一,2026-13-45,2026-04,""not-a-datewith<or=The unparsable value behaves like the earliest possible time. A function is called by an LLM or a script, so a value like
2026年4月20日is a realistic input, and the caller gets a plausible but wrong count. Wrapping the parameter asdatetime($since)ordate($since)already rejects the same values with a clear 400; only the bare comparison skips the check. The save-timeexplaincannot catch this, because it runs with the valid example value.Suggested: validate a string compared with a date or datetime property the same way
datetime()does. Until then, the docs and thecypher_generationprompt should writedatetime($x).2.
IN $listwith an empty list fails the call.WHERE m.material_code IN $codeswithcodes: []returns status 400: "IN over nothing matches nothing, so leave the condition out instead". A function author cannot leave the condition out, and the caller expects an empty table (ornull/0for a value function). Suggested: treat an empty list as matching nothing and return no rows.3.
LIMIT $nandSKIP $nare refused at save time."LIMIT takes an integer literal". A "top N" or paged function needs a separate function for each N. Suggested: accept an integer parameter, checked against the 10000 cap at call time.
4. Optional filters cannot be written.
WHERE ($code IS NULL OR p.supplier_number = $code)is refused ("IN and IS NULL apply to variable.property"), and so is($only_open = false OR p.rowclosestatus_title <> '已关闭')("a comparison must read a property"). Every input of a Cypher function is also required. Together this means one function per combination of filters, and abooleaninput can only be compared with a property.5. Arithmetic across two
WITHstages is refused with a misleading message.Refused:
"d" reads purchase_fixedleadtime outside an aggregate. Projectingltin the firstWITHworks, and so does returninglt, availwithout the subtraction, so the statement is valid. This blocks the natural shortage query: sales order → product → BOM → material, then inventory, thenneed - avail.6. Function support is narrow, and the refusal does not say which function.
round(),toUpper()andsize()all come back as "function calls is not supported".coalesce(),CASE,date()anddatetime()work. Also refused:collect()next tosum()("run the count as a query of its own"), and list slicingcollect(x)[0..3]. Suggested: name the function in the message and list the supported ones in the Cypher manual.7. Numbers come back in two forms.
sum()of a decimal property returns the string"13833141.000000", whilesum(a) * 100.0 / sum(b)returns the float72.8975147437592. The value function's output schema leaves the type open ("A string, a number, a boolean or null"). A consumer that compares values or does arithmetic has to handle both forms. Keeping decimals as strings protects precision, so this is mainly a documentation gap: the call guide and the output schema should state it.8. Multi-hop queries come close to the 55 s statement limit.
OPTIONAL MATCHon inventory, 507 materials): 39 s.STARTS WITH $prefixfilter, and timed out at 55 s without one.The caller timeout works (
timeout: 3and5give 504 on time), but a caller using the default timeout waits close to a minute before it fails.9. Error details mix two languages and wrap twice.
Example:
the cypher statement was refused: Cypher 查询使用了本接口不支持的语法。请改写为受支持的只读子集。: line 1:213: .... The useful part is the last clause; the Chinese sentence in the middle repeats bkn-backend'sdescription.10. Editing without
script_typenames the wrong cause.An edit that omits
script_typegets "a cypher function cannot become a python function; create a new function instead". This is documented (the field defaults topython), but the message reads as if the caller asked to switch languages. Suggested: default to the stored type, or say thatscript_type: cypheris missing.What passed
45 calls, grouped
x' OR '1'='1null;null(treated as a value)po2supsum, decimal property, property that is null"13833141.000000","839.999964",nulllimited: falselimited: trueIN $codeswith two codes; a string instead of a list>= $sincewith2026-04-20and with2026-04-20T00:00:00+08:00numberinput:1000,"1000","abc", missingWITHaggregate +ORDER BY(material mix per supplier)DISTINCTCASEover two aggregates,coalesce()WITH … WHERE avail > …after an aggregateIN, string-to-number conversion, extra body field, 229 rows, timeout/debugSET,DELETE), unknown property, unknown label, unknown network, syntax error$namewith no input, input without description$nmwithout input; to pythonProxy calls also need the toolbox published and the tool enabled; new functions start disabled.
/debugneeds neither. That is the same for Python functions.Not covered
execute_tooland through a logic property binding.Problems 1–6 are in bkn-backend's Cypher compiler, problems 9–10 in execution-factory, and 7–8 need docs more than code. I can open separate issues if that is easier to track.
sh00tg0a1 commentedon Nov 11, 2026
Fixes for the verification report above are up:
IN $listwith[]matches nothing; arithmetic over a property of a node aWITHgrouped by; refused functions are named. Also execution-factory's explain placeholder for a string input becomes2000-01-01, so the date check does not refuse functions saved without examples.script_typekeeps the stored type; refusals carry bkn-backend's details only.SKIP $n/LIMIT $n, under the same 10000 ceiling.All three were run against
supply_ontology_handon the verification environment beside the deployed services. Suggested merge order: #3075, #3076, #3077.Not addressed here: problem 4 (optional filters) needs its own design; new functions such as
round()(problem 6) are a feature; problems 7 and 8 are documentation.sh00tg0a1 commentedon Nov 11, 2026
Follow-up on the supply chain verification report (#2670 (comment)). With these PRs, every problem in the report has a fix or a doc note.
round()/toUpper()refusedtoFloat()/round()to get numbersLIMIT $nwarning, reference update)What a caller can now write:
On the VM, the first statement, saved as a function with two
required: falseinputs, returns 4, 16, 800 and 800 for both inputs given, one given, none, and both null. The second returns 72.9 for supplier 59202002.Merge order:
docs(execution-factory): say how Cypher functions return numbers and …
feat(bkn-backend): make a Cypher condition optional with $name IS NULL (
feat(bkn-backend): add round, abs, toUpper, toLower, toString, toInte…
fix(execution-factory): follow the Cypher changes in the function wor…
feat(bkn-backend): add round, abs, toUpper, toLower, toString, toInte…
docs(knowledge-network): list the Cypher scalar functions in the refe…
feat(execution-factory): let a Cypher function input be optional
feat(execution-factory): let a Cypher function input be optional (#1316)