# R0.1 browse over gRPC — StartTagQuery takes an OData filter (2026-06-21) Live-probed `RetrievalService.StartTagQuery` / `QueryTag` against a real **2023 R2** server over the gRPC front door (string-handle = uppercase Open2 storage GUID). Key result: **browse is feasible on 2023 R2** — the 2020 WCF "metadata-server pipe" wall does **not** block here. ## StartTagQuery — CRACKED `StartTagQuery(strHandle, btRequest)` where `btRequest` = the native `marker(26449) + version(1) + WriteHistorianString(filter)` buffer (`HistorianTagQueryProtocol.CreateStartTagQueryAttempt`). The server runs `CMdServer::StartTagQuery::StartActiveTagnamesQuery` over `\\.\pipe\aahMetadataServer\console` and **parses the filter string as OData** (not SQL-LIKE). Swept filters: | filter | result | |---|---| | `startswith(TagName,'Sys')` | ✅ success, 8-byte response | | `contains(TagName,'Sys')` | ✅ success | | `TagName eq 'SysTimeSec'` | ✅ success | | `` (empty) | ✅ success (all tags) | | `Sys*` / `*` | ❌ `ODataFilter ... bad token` | | `TagName like 'Sys%'` / `Name like 'Sys%'` | ❌ rejected | Success response `btResponse` is the 8-byte `(queryHandle:uint, tagCount:uint)` pair (`ParseStartTagQueryResponse`). Live: `startswith(TagName,'Sys')` → tagCount = 220. **Implication for the public API:** browse must translate the SDK's glob filter to OData — `*` → empty, `Pre*` → `startswith(TagName,'Pre')`, `*sub*` → `contains(TagName,'sub')`, exact → `TagName eq '...'`. (Escaping single-quotes in names still TBD.) ## QueryTag — OPEN (needs native RE of aahClient.dll) `QueryTag(strHandle, uiQueryHandle, btRequest)` is the paging call that returns the tag-name rows. Every `btRequest` shape tried returns the constant native error **type 4 / code 72 = `InvalidPacketId`** (`ArchestrA.CloudHistorian.Contract.ErrorCode.InvalidPacketId = 72`; the empty buffer alone gives a different code). So the `btRequest` must carry a **packet-id header specific to QueryTag** that we don't have — the generic `0x6751`/version-1 header (which StartTagQuery accepts) is rejected here. **Semantic fields are known** from `ArchestrA.CloudHistorian.Contract`: - request `QueryTagRequest` = `QueryHandle:uint("q") + QueryType:ushort("t") + StartIndex:uint("s") + TagCount:uint("c")` - response `QueryTagResponse` = `QueryHandle:uint + TagNames:string[]("t") + NextIndex:uint("i") + TagMetadataBuffer:byte[]("tb")` — so QueryTag returns the names directly (plus an optional metadata buffer), and pages via StartIndex/NextIndex. What's missing is the **binary `btRequest` packet framing** (the QueryTag packet id + how those fields are laid out). That serializer lives in **native `aahClient.dll`** — `aahClientManaged.dll` is **mixed-mode (C++/CLI)** so ilspycmd cannot decompile it, and no managed assembly builds the buffer. Completing QueryTag therefore requires **native RE (Ghidra/IDA on `aahClient.dll`)** or a **live gRPC capture** of the stock 2023 R2 client browsing. Do not ship a guessed QueryTag request (project discipline: no guessed wire bytes). Probe helpers live in `Grpc/HistorianGrpcTagClient` (`ProbeStartTagQuery`, `ProbeTagQuerySequence`) and the gated `StartTagQuery_OverGrpc_AcceptsODataFilter` test pins the StartTagQuery+OData result.