gRPC events: answer hypothesis #4 (SQL ground truth) — event store is global, not connection-scoped

Pursued the server-side SQL angle for the gRPC event zero-rows. Built a read-only
SOCKS5-relay + Microsoft.Data.SqlClient probe (gitignored, artifacts/.../sqlschema/)
and dumped the live Runtime event schema. Findings:

- No per-connection / per-client / per-session column exists anywhere in the event
  store. The only scoping-like columns on Events/EventHistory/snapshots are event
  content (Source_* origin, User_* acker, Provider_NodeName, SourceServer replication).
- The rich Events view is not a relational table — it is served live by the Historian
  engine via the INSQL OLE DB provider (linked servers INSQL/INSQLD; encrypted remote
  view). The SQL EventHistory base table holds only 168 rows / 1 tag.
- Decisive: for the SAME -90d..now window the gRPC StartEventQuery diagnostic returned
  0 rows, the Events view via INSQL returns 71,332 events (most recent Alarm.Set firing
  seconds before the probe). Same engine, same window — INSQL serves the data, gRPC
  withholds it from our connection.

So there is nothing in the data to scope by: the zero-row gate is the gRPC
RetrievalService's per-connection in-process execution state, not data scoping or
transport (the same class of wall as DeleteTagExtendedProperties). Combined with the
transport disproof, three independent angles are now exhausted — client payload
(byte-identical), transport (HTTP/2 == gRPC-Web), and data store (global, unscoped).
gRPC event-row retrieval stands documented as auth-solved / retrieval-server-gated;
ReadEventsAsync over gRPC keeps the no-row throw and event reads use WCF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
Joseph Doherty
2026-06-23 13:05:19 -04:00
parent b0388e7a40
commit f19eb3b821
@@ -381,15 +381,54 @@ live-server access reference.
TLS-decrypting mitm on the loopback forward — to see any connection-level header/affinity our capture TLS-decrypting mitm on the loopback forward — to see any connection-level header/affinity our capture
can't see. can't see.
4. **Server-side ground truth.** Via the SOCKS→SQL relay (user-authorized, read-only), inspect the 2. **TLS client identity / certificate.** The native used `SecurityMode=TransportCertificate`. Determine
`Runtime.dbo.Events` schema for any per-connection / per-client / source-session column that would whether it presents a **client certificate** the server uses to scope events (our SDK presents none —
explain why the server returns the rows to the native connection but not ours. Also check whether the `AllowUntrustedServerCertificate=true`, server cert only). TEST: capture the TLS handshake (e.g.
StorageService/event-store path has a connection-scoping notion the History-service event query `SSLKEYLOGFILE` + Wireshark, or a decrypting proxy) for a native `capture-event` run and check the
depends on. Certificate message; if a client cert is presented, replicate it. **Lower-probability after #1: the
plain-HTTP/2 path presents no client cert either, yet auth + registration still succeed and the gate
persists — so the gate is not at the TLS-identity layer the cert would affect.**
If 14 don't crack it, the realistic conclusion is that gRPC event-row retrieval has a server-side 3. **HTTP/2-level capture.** The byte[]/handle capture is RPC-payload only. Capture the actual HTTP/2
connection-identity dependency not reachable from a pure-managed client, and it stays documented as frames (HEADERS/SETTINGS/stream IDs, connection reuse) for the native run vs ours — via a
auth-solved / retrieval-connection-gated. TLS-decrypting mitm on the loopback forward — to see any connection-level header/affinity our capture
can't see.
4. ~~**Server-side ground truth.**~~ **ANSWERED 2026-06-23 (DISPROVES the data-scoping premise).** Via
the SOCKS→SQL relay (read-only; `artifacts/.../sqlschema/`, gitignored), dumped the full event schema
on the live `Runtime` DB. Findings:
- **No per-connection / per-client / per-session column exists anywhere in the event store.** The only
"scoping-like" columns on `Events`/`EventHistory`/snapshots are event *content*`Source_*` (event
origin area/object/PV), `User_*` (who acknowledged), `Provider_NodeName` (alarm provider node),
`SourceServer`/`SourceTag` (cross-server replication). None is "which client connection requested
this."
- **The rich `Events` view is not a relational table — it is served live by the Historian engine via
the `INSQL` OLE DB provider** (`sys.servers` shows linked servers `INSQL` + `INSQLD`;
`OBJECT_DEFINITION('dbo.Events')` is `NULL` = encrypted remote view). The Historian's own
`EventHistory` base table holds just 168 rows / 1 tag (the internal event-tag detector log); the
alarm/event journal the gRPC query reads lives in the engine, surfaced through INSQL.
- **Decisive: same engine, same `-90d..now` window, two paths diverge.** The `Events` view (via INSQL)
returns **71,332 events** for that window — most recent `Alarm.Set` firing seconds before the probe
(live, every few seconds) — while gRPC `StartEventQuery` for **our** connection returns **0**. The
data is global, abundant, recent, and identical-window-addressable; the engine simply does not hand
it to our gRPC connection.
→ There is **nothing in the data to scope by**, so the zero-row gate is **not** data scoping. It is the
gRPC RetrievalService's **per-connection in-process execution state** — the same class of wall as
`DeleteTagExtendedProperties` (server-side native in-process working-set, not reconstructable from
byte-identical wire requests). Reproduce: `artifacts/.../sqlschema/` (Program.cs = SOCKS5 relay +
`Microsoft.Data.SqlClient`; authenticate with the server's SQL login, not the domain Historian acct —
creds in the gitignored creds file).
**Conclusion (after #1 disproven + #4 answered).** Three independent angles are now exhausted: client
payload (byte-identical), transport (native HTTP/2 == gRPC-Web, both 0 rows), and data store (global,
unscoped, 71,332 events the engine serves via INSQL but withholds from our gRPC connection). The gate is
a **server-internal per-connection retrieval working-set** that a pure-managed client cannot reconstruct
by matching wire bytes, transport, or data. The remaining angles (#2 client-cert, #3 HTTP/2-frame
capture) are low-probability — #1 showed auth+registration succeed with no client cert over plain HTTP/2
and the gate still holds. **gRPC event-row retrieval stands documented as auth-solved /
retrieval-server-gated**; `ReadEventsAsync` over gRPC keeps the honest no-row throw, and event reads use
the WCF transport.
**2 of 3 layers cleared** (key exchange + client key); the 3rd (token construction) is localized to a **2 of 3 layers cleared** (key exchange + client key); the 3rd (token construction) is localized to a
specific managed method, pending dnlib extraction. ExchangeKey + the v8 serializer are committed; the specific managed method, pending dnlib extraction. ExchangeKey + the v8 serializer are committed; the