Repository navigation
fix(runtime): send realtime query params in the URL query string - #401
Conversation
Every SDK query server in server mode reads params only from the URL query string into a map of strings, but the realtime executor sends them only as a JSON body, so handlers always see empty params. These tests run the executor against a stand-in bot server that reads params the way the SDKs do. They cover strings, numbers, booleans, null, objects and arrays (as JSON text), characters that need encoding, a path that already carries a query string, and a malformed one. The no-params and path-only cases pass today and guard against a stray "?" or a rewritten path query; the rest fail until the executor adds the params to the URL.
The realtime executor POSTed params only as a JSON body, while every SDK query server in server mode reads them only from the URL query string, so realtime handlers always saw empty params. Scheduled queries were unaffected because they pass params through QUERY_PARAMS. The executor now also puts the params in the query string and keeps the JSON body unchanged, so the fix ships with the runtime alone and a later SDK change can read the typed body. Strings go as themselves and every other value as its JSON text, so handlers receive strings in server mode for now. A param replaces a key of the same name already in the path, because SDKs disagree on which of a repeated key wins, and a malformed query string in the path fails the query instead of silently losing part of it.
mini-code-review: merge after fixing the null paramBugs
Design/DRYNone. Nits
Tests: an httptest server reads params the way the SDKs do and covers strings, numbers, booleans, objects, arrays, encoding, no params and path query strings. Two atomic commits: failing tests, then the fix. |
SDK handlers read URL params as strings and fall back to a default for a
missing key, so sending a null param as the text "null" hands them a
truthy string where they expected their default (Python's
req.get("limit", 10) gets "null" instead of 10).
The null case now expects the param to be absent from the query string
while the JSON body still carries it, and a query with only null params
must add no query string at all. Both fail until the executor skips
nulls when building the query string.
SDK handlers read URL params as strings and fall back to a default for a missing key, so a null param sent as the text "null" reached them as a truthy string instead of letting the default apply. The executor now skips null params when building the query string; the JSON body still carries them, and a query with only null params adds no query string.
Security scan summaryPolicy: visibility-only target; findings are reported here and would block release if this targeted
Accepted residual findings listed in Scan coverage
Full JSON reports and compact per-scan summaries are attached as workflow artifacts. Result: no release-blocking findings detected. |
Background
Realtime (server mode) queries never delivered their params.
RealtimeExecutor.Executeinruntime/internal/query/realtime.goPOSTs tohttp://<pod-ip>:9476/<path>with the params only as a JSON body, but every SDK query server (Python, Node, Rust, .NET, Scala, Haskell, C++) reads params only from the URL query string into a map of strings. Handlers in realtime bots therefore always saw empty params. Scheduled (ephemeral) queries pass params throughQUERY_PARAMSand were not affected; that path is unchanged. No prod bot is realtime, so nothing live was affected.The fix is runtime only, so it ships with a runtime release and needs no SDK release. The JSON body stays as it is, so a later SDK change can read typed params from the body and fall back to the query string.
Behaviour of the params in the query string:
100,0.25,true,{"from":"2026-01-01"},["BTC","ETH"]), all URL encoded withurl.Valuesreq.get("limit", 10)in Python) and the string"null"would defeat that?), and a path that already carries a query string is sent untouchedTasks
Tested
New tests in
runtime/internal/query/realtime_test.go, grouped by behaviour:TestRealtimeExecutor_ParamsReachBotAsURLQueryString: string, whole and fractional numbers, booleans, nested object, array, and reserved plus non-ASCII characters in both key and valueTestRealtimeExecutor_NoParamsLeavesQueryStringOut: nil and empty params send exactly/portfolio, with no?TestRealtimeExecutor_NullParamsStayOutOfQueryString: a null param next to other params is absent from the query string while the JSON body still has it; a query with only null params sends exactly/portfolioTestRealtimeExecutor_QueryStringInPathCombinesWithParams: a path query string passes through untouched without params; params join it and win on the same key, each key sent once; a malformed path query string is rejected without querying the botThe existing
TestRealtimeExecutor_ExecuteWithParamsstill passes, so the JSON body is unchanged.Commit structure:
test(runtime): expect realtime query params in the URL query stringadds the tests only. At this commit the params cases, the merge case and the malformed case fail (the bot server sees an empty map); the no-params and path-only cases pass and act as guards.fix(runtime): send realtime query params in the URL query stringmakes them pass. At this point a null param was sent as the textnull.test(runtime): expect null realtime params only in the JSON bodyreplaces the "null arrives as JSON text" case with the null tests above. Both fail at this commit (cursor=nullreaches the bot); their body assertions already pass.fix(runtime): leave null realtime params out of the query stringmakes them pass.Commands run in
runtime/(after commit 2, and again after commit 4):go build ./...: okgo vet ./...: okgo test -short -count=1 ./...: all packages okgo test -short -count=1 -race -run TestRealtimeExecutor ./internal/query/: okgofmt -lon the touched files: cleanTech Debt
availablelist (SDK side, tracked with the bot contract work).