Commit Graph

10 Commits

Author SHA1 Message Date
Joseph Doherty 428b8db024 test(dcl+communication+storeandforward+kpihistory+host): close register row-40-class sleeps + row-32 provider leak (arch-review 2026-08-19 P8-8 + P8-9)
P8-8: replace the remaining wall-clock sleep-then-assert instances outside
Communication.Tests' Medium-wave sweep with the same row-40 house pattern —
continuous polling (AssertHoldsFor) for ABSENCE assertions instead of a single
end-of-window sample, which cannot distinguish "stayed quiet throughout" from
"hadn't happened yet when the one sample landed" and cannot catch a value that
flips wrong and back before that sample.

- DataConnectionActorBatchTests: QualityCounters_ArePushedOnTransitionOnly_AndCoalesced
  (:206/:214 class) converted to AssertHoldsFor; ResolutionProbes_BackOff...
  (:181/:184, a genuine real-time backoff-RATE assertion, not a sync-primitive
  stand-in) converted to AssertRoundsWithinWindow, which enforces the ceiling
  continuously (fails fast on a no-backoff regression) while the floor still
  necessarily waits out the window.
- SiteAlarmLiveCacheServiceTests (:452-454, :470-472): converted to the
  project's own AssertHoldsFor (mirrors SiteAlarmAggregatorActorTests', same
  assembly).
- StoreAndForwardServiceTests (:891-892): async AssertHoldsForAsync variant
  (the class runs on the async test thread).
- KpiHistoryRecorderActorTests (:1043-1049): two skip-path FoldCount==0
  absence checks converted.

Each helper is a private per-file copy, matching the Medium-wave T17
precedent (SiteAlarmAggregatorActorTests.AssertHoldsFor) rather than a shared
TestSupport utility. Proof of the mechanism: a standalone (non-repo) harness
run 100 trials of a real background thread flipping a boolean true then back
to false inside a 300ms window — Thread.Sleep(300)+one sample missed the
violation 100/100 times (false pass), continuous 20ms polling caught it
100/100 times. Most of the actual conversions here guard monotonic counters
(their single-sample variant would eventually see the final value too), so
each converted test carries an inline honesty note on what specifically the
conversion buys for that assertion (fail-fast vs. true flip-back detection).

P8-9: ScadaBridgeTelemetryTests.EmitHelpers_DoNotThrow called
SetQueueDepthProvider(() => 5) against the process-global row-32 provider
slot with no clear and no [Collection] — a landmine for any future
same-process reader of scadabridge.store_and_forward.queue.depth. Fixed with
compare-and-clear in a finally via the existing (production, untouched)
ClearQueueDepthProvider. Searched this assembly and the whole test tree for a
second same-process reader that asserts on the value: none exists today
(MetricsEndpointTests scrapes /metrics but only asserts on the response
shape, and the only value-asserting test, QueueDepthGaugeTests, lives in
StoreAndForward.Tests — a separate test-host process the leak cannot reach),
so no [Collection] was added; the test's doc comment records the search and
flags it for re-check if that changes.

Test-only change, zero production edits. Full suites green: DCL.Tests
342/342, Communication.Tests 729/729, StoreAndForward.Tests 135/135,
KpiHistory.Tests 54/54, Host.Tests 522/522. Each converted test additionally
looped 20x in isolation with zero flakes.
2026-08-19 16:41:04 -04:00
Joseph Doherty 7b95ef3752 fix(kpi): backfill skips or shrinks to the rollup watermark on failover restarts (plan R2-04 T4) 2026-07-13 10:03:33 -04:00
Joseph Doherty 350d530135 fix(kpi): slice the rollup backfill into bounded 24h folds that yield to periodic folds (plan R2-04 T3) 2026-07-13 09:57:42 -04:00
Joseph Doherty a1c7c4ef26 feat(kpi): GetLatestRollupHourAsync watermark seam for the backfill fast-path (plan R2-04 T2) 2026-07-13 09:44:57 -04:00
Joseph Doherty bedf3c55f0 feat(kpihistory): one-shot rollup backfill of existing samples on recorder start (plan #22 T6)
Fold the whole raw-retention window of already-recorded KpiSample rows into
KpiRollupHourly once shortly after the recorder singleton starts, so long-range
(30 d/90 d) trend charts aren't blank until enough wall-clock passes after
deploy. Reuses Task 3's idempotent FoldHourlyRollupsAsync over
[TruncateToHour(now) - RetentionDays, TruncateToHour(now)); runs at most once
per actor lifetime (_backfillDone latch) and defers past / blocks the periodic
lookback fold via a dedicated _backfillInFlight guard so the two idempotent
upserts never race on overlapping rows. Best-effort: no exception escapes; logs
start, window, and elapsed on completion. No Host or options change.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-10 12:07:47 -04:00
Joseph Doherty 4f145fd62a feat(kpihistory): hourly rollup recorder tick + rollup purge + options (plan #22 T4)
Add a third Akka periodic timer (kpi-rollup) to KpiHistoryRecorderActor that
folds the trailing RollupLookbackHours of raw KpiSample rows into the hourly
KpiRollupHourly table via the idempotent FoldHourlyRollupsAsync upsert, with the
in-progress hour excluded (toHourUtc = current hour start, exclusive). A
_rollupInFlight guard coalesces overlapping ticks (mirrors _sampleInFlight), the
fold is best-effort (no exception escapes a tick), and a missed/failover tick
self-heals via the lookback re-fold. The daily purge now runs BOTH the raw
PurgeOlderThanAsync (RetentionDays) and PurgeRollupsOlderThanAsync
(RollupRetentionDays), isolated so one failure never skips the other.

New KpiHistoryOptions: RollupInterval (1h), RollupLookbackHours (3),
RollupRetentionDays (365), RollupThresholdHours (168, consumed by Task 5).
Validator adds bounds incl. the coherence rule RollupRetentionDays >=
RetentionDays so long-range trends have no data hole.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-10 11:58:50 -04:00
Joseph Doherty fd618cf1dc fix(review): full code-review remediation — 5 High + Medium/Low across 16 modules
Remediation from the full per-module code review at 4307c381 (findings recorded
separately in code-reviews/).

Highs fixed:
- DeploymentManager-025/SiteRuntime-031: stop broadcasting notification lists + SMTP
  configs (incl. credentials) to sites; site purges already-persisted rows on apply
  (enforces the central-only delivery design; clears plaintext SMTP creds at rest).
- DataConnectionLayer-023: guard the native-alarm subscribe path against the
  mid-flight-unsubscribe adapter-feed leak (mirrors the DCL-021 tag-path fix).
- SiteEventLogging-024: normalize From/To query bounds to UTC (the -016 fix the
  audit trail claimed but never committed).
- KpiHistory-001: add an in-flight guard to the recorder sample tick.
- ScriptAnalysis-001: harden the trust analyzer's TPA-absent fallback (resolve
  forbidden anchors in the minimal reference set; warn on degraded mode) — anchors
  added to validation references only, never the compile gate.
(InboundAPI-026 left to the feat/ipsen-movein effort per owner decision.)

Medium/Low: DM-026 deterministic deploy-status tiebreaker; SR-027/028/029/030
native-alarm leak/phantom-active/delete-during-redeploy fixes; AL-013/014/016;
TE-024 (folder-mutation audit rows now persisted)/025; SF-025 gauge-provider
clear-on-stop; ESG-025/026; SEC-023/024/025; SCA-007/008/009; plus doc/test
accuracy COM-023/024, HOST-025/026, HM-024/025, NS-027/028.

Full-solution build 0 warnings; ~3560 tests across 18 touched suites green.
2026-06-20 17:55:12 -04:00
Joseph Doherty cb2a516187 refactor(kpi): K4/K10/K12 review fixups — test data-race + faulted-tick liveness, dead-branch/unused removal, NaN-guard assertions, value clamp + doc 2026-06-17 20:15:47 -04:00
Joseph Doherty 9c2e7ab4cb feat(kpi): K4 — KpiHistoryRecorderActor (best-effort sampling + daily purge) 2026-06-17 20:06:09 -04:00
Joseph Doherty 9ffa34d3e7 feat(kpi): K3 — KpiHistory project + options/validator + AddKpiHistory 2026-06-17 19:48:59 -04:00