# 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 — CRACKED (2026-06-21), browse SHIPPED `QueryTag(strHandle, uiQueryHandle, btRequest)` is the paging call that returns the tag-name rows. The blocker was the packet id: every guessed `btRequest` returned native error **type 4 / code 72 = `InvalidPacketId`** (`ArchestrA.CloudHistorian.Contract.ErrorCode.InvalidPacketId`). The generic `0x6751` header that StartTagQuery accepts is the **wrong** id for QueryTag. **How it was found (no Ghidra needed):** a `.rdata` **packet-descriptor table** in `aahClientManaged.dll` lists consecutive `{uint marker, uint version}` entries — `{0x6751, 1}` (StartTagQuery) immediately followed by **`{0x6752, 1}`** (the paired op). Found by `pefile` byte-scan of `.rdata` for `51 67 00 00` and dumping the surrounding dwords. Testing `0x6752` live confirmed it. **QueryTag wire format (live-verified):** - request `btRequest` = `u16 marker(0x6752) + u16 version(1) + u16 queryType + u32 startIndex + u32 count` — `queryType = 1` returns tag-name rows (`queryType = 0` returns an empty/count-only page). - response `btResonse` = `u32 count + per-name(u32 charCount + UTF-16LE) + trailer` (the trailer is the CloudHistorian `NextIndex`/`TagMetadataBuffer` region — ignored by `HistorianTagQueryProtocol.ParseTagNameQueryPage`). - Semantic fields match `ArchestrA.CloudHistorian.Contract.QueryTagRequest` (`QueryType/StartIndex/TagCount`; the QueryHandle travels in the protobuf `uiQueryHandle`). **Browse is shipped:** `HistorianClient.BrowseTagNamesAsync` routes over gRPC when `Transport==RemoteGrpc` via `Grpc/HistorianGrpcTagClient.BrowseTagNamesAsync` (StartTagQuery(OData) → paged QueryTag(0x6752) → EndTagQuery), with the SDK glob filter translated by `GlobToODataFilter`. Golden-byte + glob unit tests and a gated live test (`BrowseTagNamesAsync_OverGrpc_ReturnsSystemTags`) cover it. **M0 gRPC parity is complete.**