Files
ScadaBridge/docs/plans/2026-08-15-residual-remediation-plan.md
T

141 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Residual Remediation Plan — the seven remaining deferred items
**Date:** 2026-08-15 · **Base:** `main` @ `f228ac22` · **Execution:** one opus subagent per item,
parallel git worktrees, merged by the orchestrator in conflict-safe order, single rig
redeploy + verification pass at the end.
Scope = the seven items left open after the arch-review remediation program closed residuals
#1 (DCL counter drift, `491df111`) and #7 (target-scale load test, `40eff637`). Sources:
residuals register in `2026-08-15-arch-review-remediation-execution-log.md` and deferred-work
register rows.
## Execution rules (all agents)
- Verify worktree base is `f228ac22` or a descendant before starting; branch names below.
- **Do NOT edit** `docs/plans/2026-07-08-deferred-work-register.md` or the execution-log
residuals register — 7 parallel editors of one file guarantees conflicts. Report closure
text + any **candidate new register rows** in the final report; the orchestrator records
them centrally.
- **Rig ownership:** only R6 (Playwright) may touch the `docker/` rig. R1/R2 are code+tests
only; the orchestrator does one consolidated `bash docker/deploy.sh` + live probe after all
merges.
- Component docs / CLAUDE.md updates travel with the change per repo rules. No pushes anywhere.
- Build `dotnet build ZB.MOM.WW.ScadaBridge.slnx` clean; run the tests your change touches.
## R1 — Per-table `needs_snapshot` in LocalDb (cross-repo) — branch `localdb-per-table-snapshot`
**Problem:** baselining one table (`RegisterReplicated(..., baselineExistingRows: true)`) sets a
global `needs_snapshot`, so enabling replication for one new table re-streams every registered
table in both directions.
**Approach:** in `~/Desktop/scadaproj/ZB.MOM.WW.LocalDb` (branch off its `main` @ `f627cee`):
move the flag per-table (bookkeeping schema v3, upgraded in place on open, like v1→v2 was).
Snapshot phase streams only flagged tables. Wire compatibility is the hard constraint: extend
the snapshot negotiation **additively** (absent per-table scoping field ⇒ full snapshot, so
0.1.x/0.2.x peers keep syncing). Crash safety: a table's flag clears only after *that table's*
snapshot is acked. Version per library convention (schema change, wire-compatible ⇒ 0.3.0).
Update the library README + `RegisterReplicated` remarks (which name this as the follow-up).
Publish to the Gitea feed via `build/push.sh` only after the library suite is green. Then, in
the ScadaBridge worktree: bump `Directory.Packages.props`, build, run the LocalDb-touching
suites, update the CLAUDE.md LocalDb bullet + the umbrella `~/Desktop/scadaproj/CLAUDE.md`.
**Verify:** two-node integration test proving single-table baseline streams only that table;
mixed-version interop test (old peer ⇒ full snapshot); ScadaBridge suite green on the bump.
## R2 — gRPC event batching — branch `grpc-event-batching`
**Problem:** every `AttributeValueChanged`/`AlarmStateChanged` rides one gRPC message; at target
scale that is 37.5k messages/s/site of per-message overhead.
**Approach:** additive wire shape in the vendored proto (`Communication/SiteStreamGrpc/`,
manual toggle-build-copy-untoggle regeneration; field numbers never reused): a batch message
(repeated events) exposed via a **new** RPC or additive field so old peers are untouched —
new-central↔old-site and old-central↔new-site must both keep working on the existing path.
Server coalesces per subscriber under a small bounded window (size + time cap, e.g. ≤100
events / ≤25 ms, configurable via options, validated); client unpacks and feeds the existing
per-event pipeline so timestamps, generation fencing, reconnect-on-graceful-completion, and
`IsLive` semantics are untouched. Batch-size histogram metric. Note interplay with register
row 31: batching sits downstream of the shared publish stage and does not change the burst
ceiling — say so in the component doc.
**Verify:** batcher unit tests (window/cap/flush-on-close), client/server round-trip
integration, version-skew tests both directions, existing Communication suites green.
No rig redeploy (orchestrator does it post-merge).
## R3 — Deployments page server-side paging + status counts — branch `deployments-server-paging`
**Problem:** the Central UI Deployments page client-materializes the full deployment list.
**Approach:** find the page's data path first; add a paged repository query (EF, following the
repo's existing paged patterns — Audit Log keyset paging is the reference) plus a status-count
aggregation query (counts by status computed server-side, one grouped query). Wire through
whatever service layer the page uses; UI gets paging (pager or `Virtualize`, matching an
existing page's idiom), 500 ms filter debounce, and status-count tiles. Bootstrap only, no
third-party components; match the corporate UI conventions.
**Verify:** repository paging + count tests; existing CentralUI/ConfigurationDatabase suites
green; page behavior exercised via whatever bUnit/unit seam exists for sibling pages.
## R4 — OtOpcUa LocalDb pin bump — branch (in `~/Desktop/OtOpcUa`) `localdb-bump`
**Problem:** OtOpcUa pins LocalDb 0.1.3 — supported skew, but drifting further as LocalDb moves.
**Approach:** bump to the newest LocalDb on the Gitea feed (0.2.1 now; if R1's 0.3.0 lands on
the feed before you finish, take that instead). Reconcile API deltas (0.2.x added
`DeregisterReplicated`, `MaxBatchBytes`, `baselineExistingRows`; check what OtOpcUa's usage
touches). Build + full OtOpcUa test suite. Update its own docs/CLAUDE.md where the pin is
recorded, and the umbrella `~/Desktop/scadaproj/CLAUDE.md` if it records the skew.
**Verify:** OtOpcUa builds clean, full suite green. No push.
## R5 — SandboxTests timing pin + flake-pattern sweep — branch `sandbox-timing-fix`
**Problem:** a fragile timing pin in the SandboxTests (noted pre-existing during the
remediation program); the AwaitAssert-then-bare-assert flake class it belongs to had 3 other
instances root-caused and fixed (`c4caebe9`, `cfa6acbf`).
**Approach:** locate the pin, root-cause it causally (delay-injection reproduction + negative
control, same discipline as the prior flake fixes), replace the timing assumption with
deterministic synchronization — test-side only unless the product is genuinely wrong. Then do
the deferred **bounded sweep**: grep the test suites for the same pattern (AwaitAssert on one
observable followed by a bare assert on a strictly-later observable), fix what's clearly the
same class, list anything ambiguous as candidate register rows instead of guessing.
**Verify:** fixed tests re-run ≥5× green; touched suites green; fail-before-fix demonstrated
via injected delay.
## R6 — Playwright environment failures — branch `playwright-env-fixes` (owns the rig)
**Problem:** 14 Playwright failures present on `main` before the remediation branch,
rig-state related, never triaged.
**Approach:** run the suite against the current rig for a fresh baseline; catalog and classify
every failure (stale rig data assumption, timing, selector drift, genuine app bug). Fix causes
at the right layer — test fixes, or rig seed-state fixes via the ScadaBridge CLI (preferred
over DB edits). Rig redeploy allowed for this agent if needed. Anything irreducible or
app-side gets a precise writeup as a candidate register row rather than a papered-over test.
**Verify:** clean full-suite re-run; report before/after failure counts with per-test root
causes. A genuine app bug found here is a finding to report, not necessarily to fix.
## R7 — `site_events` purge oplog-visibility operator note — branch `site-events-purge-note`
**Problem:** WP3.2's sliced retention DELETE is CDC-captured on a replicated site (correct by
design — no separate resync path), so a purge burst shows up in `LocalDbOplogBacklog`; an
operator watching the gauge can misread it as a replication problem.
**Approach:** documentation + one log line, no behavior change: a note in
`docs/deployment/topology-guide.md` and `Component-SiteEventLogging.md` (what the burst looks
like, why it is expected, how it drains), plus an Information-level log when a purge on a
replication-enabled node deletes rows ("N rows purged; transient oplog backlog expected").
**Verify:** build green; the purge path's existing tests still green; log line unit-covered if
the purge path has a test seam.
## Merge & close-out (orchestrator)
Merge order: R5 → R7 → R4 (separate repo, no merge here) → R3 → R2 → R1 → R6. Then: record
all closures + candidate rows in the registers, consolidated `bash docker/deploy.sh` + live
probe (stream batching live check, replication convergence, site_events purge log line),
full-solution build + test, hold push for the user.