fix(historian): page by the backend's timestamp resolution; QuestDB AtTime stamps the requested time

IHistorianDataSource.TimestampResolutionTicks (default 1; QuestDB 10). A drained tie
cluster now steps off T by the resolution, so a forward one-value-per-page read on a
microsecond backend no longer re-reads the same sample forever (C1). QuestDB AtTime
results carry the client's original request time (M3); regression for the NEXT seek
skipping a tombstone (M1). Conformance backend wires the real writer and updater.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-11 02:30:25 -04:00
parent 13d628a07f
commit d2be213e86
9 changed files with 330 additions and 19 deletions
@@ -107,4 +107,19 @@ public interface IHistorianDataSource : IDisposable
/// </summary>
/// <returns>The current health snapshot for the historian backend.</returns>
HistorianHealthSnapshot GetHealthSnapshot();
/// <summary>
/// The backend's storage timestamp resolution, in <see cref="DateTime"/> ticks (100 ns): the smallest step
/// between two distinct stored timestamps. <c>1</c> (the default) means full tick precision; QuestDB stores
/// microseconds and reports <c>10</c>.
/// </summary>
/// <remarks>
/// The server's HistoryRead-Raw paging steps OFF a drained timestamp by this amount (forward
/// <c>T + resolution</c>, reverse <c>T − resolution</c>). Stepping by less than the resolution is not
/// progress on a backend that truncates its inputs: <c>T + 1 tick</c> is truncated back to <c>T</c>, the
/// same sample comes back, and a one-value-per-page forward read loops forever. Stepping by the resolution is
/// still lossless, because no stored timestamp can lie strictly between <c>T</c> and <c>T ± resolution</c>.
/// Same-timestamp ties are unaffected (they are paged within the timestamp, never stepped over).
/// </remarks>
long TimestampResolutionTicks => 1;
}