docs(plans): issue batch #667-#673 design
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Issue batch #667–#673 — design (2026-10-11)
|
||||
|
||||
All seven open issues ≥ #667 are testable with available infra (docker-dev rig, SQL Server, opc-plc fixture on
|
||||
10.100.0.35). Base: master `ee67b637`. Recommended choices taken throughout (user-approved).
|
||||
|
||||
## Lanes
|
||||
|
||||
| Lane | Issue | Model | Wave |
|
||||
|---|---|---|---|
|
||||
| L1 | #673 FOCAS flake | sonnet | 1 |
|
||||
| L2 | #670 OpcUaClient session loss | opus | 1 |
|
||||
| L3 | #672 crash-failover capture gap | opus | 1 |
|
||||
| L4 | #669 event HistoryRead continuation points | sonnet | 1 |
|
||||
| L5 | #667 SqlServer retention/purge | sonnet | 1 |
|
||||
| L6 | #671 time-weighted Part 13 aggregates | opus | 1 |
|
||||
| L7 | #668 Part 11 modified values | opus | 2 (after L4 + L5 merge) |
|
||||
|
||||
Each lane: isolated worktree reset to master, TDD, independent opus review, merge, full suite, docker-dev live gate.
|
||||
|
||||
## #673
|
||||
Root cause: `FocasDriver.FixedTreeLoopAsync` publishes `LastStatus` before `FixedTreeCache` / `LastFixedPollUtc`;
|
||||
the test waits on `LastStatus` only, and an authored `FIXED:Status/*` read returns `BadCommunicationError` while
|
||||
`LastFixedPollUtc` is null. Fix: test waits on all three; driver writes `LastStatus` last via `Volatile.Write`.
|
||||
|
||||
## #670
|
||||
Root causes: bad keep-alive never touches `_health` nor publishes; `LastSuccessfulRead` never advances on
|
||||
notifications/keep-alives; the SDK `SessionReconnectHandler` retries forever (exhausted path unreachable) and on a
|
||||
session recreate clones subscriptions the driver's dictionaries no longer track.
|
||||
Fix (Option B): bad keep-alive ⇒ `Degraded` (stamp preserved) + one `BadNoCommunication` publish per subscribed ref
|
||||
per outage (value retained) + quality-only alarm update; good keep-alive / notifications advance
|
||||
`LastSuccessfulRead`; a self-healed short outage re-reads and republishes Good (beats the (value,status) dedup);
|
||||
sustained loss is recovered by the #527 watchdog (shutdown + init + resubscribe). Delete `SessionReconnectHandler`
|
||||
wiring, `OnReconnectComplete`, `TryRearmReconnect`, the `reconnect_exhausted` counter. Wire SDK telemetry logging
|
||||
at Warning. Correct `docs/DriverLifecycle.md` survey.
|
||||
|
||||
## #672
|
||||
Standby opens the historization capture gate when its recording Primary is cluster-UNREACHABLE (local
|
||||
`ClusterEvent.ReachabilityEvent`, recomputed from the cached redundancy snapshot in `DriverHostActor`). Alarm drain
|
||||
gate and data-plane Primary gate unchanged (falsification tests). Knob
|
||||
`ContinuousHistorization:CaptureWhilePrimaryUnreachable` (default true). No backend capability flag. Gap shrinks to
|
||||
the failure-detector window (~10–12 s). Documented: drivers without a source timestamp (Modbus) duplicate rows in
|
||||
the overlap window.
|
||||
|
||||
## #669
|
||||
Event HistoryRead issues/resumes continuation points: cursor = last event time + trailing tie count; oversized tie
|
||||
cluster over-fetch via `[T, T+resolution)` bounded by `MaxTieClusterOverfetch`; generic `SliceTieCluster`; store
|
||||
state widened to carry an event cursor; unknown CP ⇒ `BadContinuationPointInvalid`. Reverse event reads out of
|
||||
scope. Ties exact on SqlServer/QuestDB, best-effort on Gateway (documented). Adds an over-the-wire event HistoryRead
|
||||
test.
|
||||
|
||||
## #667
|
||||
`ServerHistorian:SqlServer:{RetentionDays (null=forever), PurgeInterval=1h, PurgeBatchSize=4000}`; optional
|
||||
`IHistorianBackendProvider.CreateRetention`; `HistorianRetentionService` gated on `IHistorizationRoleView`
|
||||
plus `sp_getapplock` (timeout 0); per-tag batched `DELETE TOP(n)` on the clustered key, events by time; cutoff on
|
||||
source time; metric `otopcua.historian.retention.purged{table}`. QuestDB: no change (TTL). Partition switching
|
||||
deferred/documented.
|
||||
|
||||
## #671
|
||||
New time-weighted family: TimeAverage, TimeAverage2, Interpolative, Total (now the Part 13 time-weighted integral,
|
||||
matching Gateway — client-visible, release-noted), Total2. Bounding values outside the window + `LEAD()` in-bucket
|
||||
areas in SQL; C# splits bucket-crossing segments. Sloped for numeric, stepped for Boolean. Server-default
|
||||
AggregateConfiguration (client's ignored). QuestDB mirrored after a lag/lead support spike. Hand-computed literals
|
||||
in the shared conformance suite.
|
||||
|
||||
## #668
|
||||
`hist.SampleModified` (schema v2), prior values via `OUTPUT deleted.* INTO` in the same statement;
|
||||
`HistoryUpdateContext(Principal, ApiKeyId)` threaded into updater methods; Insert operations logged (empty prior);
|
||||
retention purges never logged but #667 purges `SampleModified`; `ReadModifiedAsync` serves `IsReadModified=true`
|
||||
with continuation points, authorized by the HistoryRead gate. QuestDB: append-only `sample_modified` written before
|
||||
the change (orphan-audit-row-on-crash, documented deviation).
|
||||
Reference in New Issue
Block a user