diff --git a/docs/plans/2026-10-11-issue-batch-674-678-design.md b/docs/plans/2026-10-11-issue-batch-674-678-design.md new file mode 100644 index 00000000..7e24f632 --- /dev/null +++ b/docs/plans/2026-10-11-issue-batch-674-678-design.md @@ -0,0 +1,70 @@ +# Follow-up batch #674–#678 — design (2026-10-11) + +Follow-ups filed after the #667–#673 batch. Base: master `bc03adb1`. User-approved; recommended choices throughout. +Investigation found three new defects (client endpoint port not rewritten; role overlays never load on +cluster-role nodes; Gateway Count/Average mode mapping) — the first two are fixed here, the third is filed. + +| Lane | Scope | Model | +|---|---|---| +| L1 | #674 HistoryUpdate posture | opus | +| L2 | #675 reverse event paging | opus | +| L3 | #677 aggregates | opus | +| L4 | client (#677.5, #678.5/.6, #674.6) | sonnet | +| L5 | #676 OpcUaClient | opus | +| L6 | #678.1–.4 + role-overlay bug | sonnet | + +## L1 — #674 +- **674.1:** spike on QuestDB 10.0.1 showed TTL is judged against `min(maxTs, wall clock)` (`cairo.ttl.use.wall.clock=true` + default) — a 2066 row evicted nothing. Add a QuestDB integration test pinning that + a docs note. HistoryUpdate refuses a + SourceTimestamp beyond `now + ServerHistorian:HistoryUpdateMaxFutureSkew` (default 1 day) with `BadOutOfRange`; the + recorder is not bounded. +- **674.2:** SqlServer base schema requirement back to v1; `SupportsModifiedTrail` (v ≥ 2). On v1: modified reads and + HistoryUpdate ⇒ `BadHistoryOperationUnsupported` (nothing sent), retention skips `SampleModified`, one Warning per boot. +- **674.3:** HistoryUpdate Primary-gated (off-Primary ⇒ `BadInvalidState`, same shape as `NativeAlarmAckPrimaryGate`). + Retry duplicates on QuestDB documented; no DEDUP key. +- 674.4/674.5 documented only. + +## L2 — #675 +- Reverse event reads: `reverse` flag on `IHistorianDataSource.ReadEventsAsync`; SqlServer/QuestDB order DESC and take + the top N; direction-aware cursor (reuse raw `SliceTieCluster(reverse:)`); Gateway ⇒ `BadHistoryOperationUnsupported` + for reverse. +- End-only event read ⇒ backward (mirrors raw). +- Offline test pinning `DateTime.MaxValue` through the Gateway proto/FILETIME encoding; live AVEVA leg stays open (VPN). + +## L3 — #677 +- Reverse processed reads per SDK/Part 13: buckets anchored at the later (start) time stepping back, partial bucket + earliest, bucket timestamp = later edge. Parity test vs SDK `AggregateCalculator`. Client-visible, release-noted. +- Partial bit also when the data starts inside a bucket (no bounding prior). +- `AggregateConfiguration`: `UseServerCapabilitiesDefaults` or values equal to the server defaults ⇒ served; otherwise + per-node `BadAggregateConfigurationRejected`. +- Gateway provisioner sends `IntegralDivisor = 1` (proto3 default 0). Live magnitude check stays open (VPN). + +## L4 — client +- `EndpointSelector.SelectBest` rewrites host **and** port of the advertised endpoint (fixes rig `:4841` cert error). +- Client aggregate enum/mapper/CLI/UI gain TimeAverage, TimeAverage2, Total, Total2, Interpolative. +- History read refusal ⇒ one-line `CommandException`, no stack trace. +- `NodeManagerHistoryReadTests` non-historized case sets `IsReadModified=false`. + +## L5 — #676 +- Driver endpoint host+port rewrite; in-process TCP-stall proxy wire test (in-place blip, same session, Good republish, + no regression). +- Remove dead `ReconnectPeriod` (options, AdminUI form, docs, tests); stored JSON with it still parses. +- New opt-in `IDriverConnectionLossSource` (Core.Abstractions): raised once per session by OpcUaClient only on a + definitive loss (`BadSessionIdInvalid`/`BadSessionClosed`/`BadSessionNotActivated`/subscription lost); + `DriverInstanceActor` (attach PreStart, detach PostStop) turns it into `DisconnectObserved` only while Connected. + Ambiguous codes keep going through the #527 watchdog. +- Information-level per-recovery replay-drop summary. Equal-timestamp drop documented. + +## L6 — small +- Retention: mid-command cancel test + `ClearPool` fallback test with falsification control. +- #672 tests: initial-state unreachable path; strengthen `Self_unreachable_is_ignored`; edge-triggered Debug log. +- FOCAS `WireBackendTests`: wait for motor speeds too; `WaitFor` fails on timeout. +- QuestDB TTL-cleared log wording; `docker-dev/docker-compose.debug.yml` Debug recipe. +- Role overlay suffix built from plain roles only — after auditing each `appsettings..json` and stripping anything + that would change production behaviour on activation. + +## Live gates (docker-dev) +1. opc-plc `docker restart` recovers in seconds (was ~100 s); pause-blip re-check. +2. Client.CLI connects to `:4841`–`:4845`. +3. HistoryUpdate refused on the Secondary, accepted on the Primary; v1-schema DB keeps historizing. +4. Reverse event paging and reverse processed reads over the wire, SqlServer + QuestDB.