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:
@@ -381,15 +381,54 @@ live-server access reference.
|
||||
TLS-decrypting mitm on the loopback forward — to see any connection-level header/affinity our capture
|
||||
can't see.
|
||||
|
||||
4. **Server-side ground truth.** Via the SOCKS→SQL relay (user-authorized, read-only), inspect the
|
||||
`Runtime.dbo.Events` schema for any per-connection / per-client / source-session column that would
|
||||
explain why the server returns the rows to the native connection but not ours. Also check whether the
|
||||
StorageService/event-store path has a connection-scoping notion the History-service event query
|
||||
depends on.
|
||||
2. **TLS client identity / certificate.** The native used `SecurityMode=TransportCertificate`. Determine
|
||||
whether it presents a **client certificate** the server uses to scope events (our SDK presents none —
|
||||
`AllowUntrustedServerCertificate=true`, server cert only). TEST: capture the TLS handshake (e.g.
|
||||
`SSLKEYLOGFILE` + Wireshark, or a decrypting proxy) for a native `capture-event` run and check the
|
||||
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 1–4 don't crack it, the realistic conclusion is that gRPC event-row retrieval has a server-side
|
||||
connection-identity dependency not reachable from a pure-managed client, and it stays documented as
|
||||
auth-solved / retrieval-connection-gated.
|
||||
3. **HTTP/2-level capture.** The byte[]/handle capture is RPC-payload only. Capture the actual HTTP/2
|
||||
frames (HEADERS/SETTINGS/stream IDs, connection reuse) for the native run vs ours — via a
|
||||
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
|
||||
specific managed method, pending dnlib extraction. ExchangeKey + the v8 serializer are committed; the
|
||||
|
||||
Reference in New Issue
Block a user