Commit Graph

37 Commits

Author SHA1 Message Date
Joseph Doherty 35ce14138c feat(centralui): server-side paging + server-computed status counts on Deployments (R3)
The Deployment Status page client-materialized the whole deployment list. It read
EVERY DeploymentRecord — an insert-only table, one row per deploy attempt for the
retention window — plus EVERY Instance, then site-scoped, sorted, counted the four
status tiles and sliced a 25-row page in the Blazor circuit's memory. That ran on
first render AND on every IDeploymentStatusNotifier push, so the cost scaled with
the age of the system rather than the size of the page.

All four jobs move into SQL:

- `IDeploymentManagerRepository.QueryDeploymentListPageAsync(filter, page, size)`
  returns one page of `DeploymentListRow` — DeploymentRecord INNER JOINed to
  Instance, so the instance display name and site travel with the rows that need
  them — plus the total count of the filtered set. The join is exact: the FK is
  Restrict and DeleteInstanceAsync removes the records first, so no orphan exists.
- `GetDeploymentStatusCountsAsync(filter)` returns the tile counts from ONE grouped
  aggregation, deliberately ignoring the filter's Status: the tiles are the status
  BREAKDOWN of the filtered set, so honouring it would zero three of four tiles the
  moment an operator clicked one.
- Site scoping runs in the query as `SiteIdScope` resolved through the record's
  instance (DeploymentRecord has no SiteId of its own). An EMPTY grant stays a real
  filter matching nothing, never "unconstrained".
- The now-callerless whole-table `GetAllDeploymentRecordsAsync` is deleted.

OFFSET paging, not the Audit Log's keyset cursor, and deliberately so: this page's
pager is numbered and jump-to-any-page, so it needs a page count, which only a total
can give it — a keyset cursor can express neither, and the total is required for the
tiles regardless. The deep-offset cost that pushes high-volume tables to keyset is
bounded here by the terminal-record retention purge, unlike the 365-day AuditLog.
This mirrors the Notification Outbox, offset-paged for the same reason. Ordering is
DeployedAt DESC, Id DESC — the Id tie-break is load-bearing, because DeployedAt ties
on rapid redeploys and an unstable sort key makes offset paging repeat or drop rows.

UI: the four status tiles become the status filter (click to apply, click again to
clear, aria-pressed, phrasing-only content so a <button> stays valid), plus a
free-text search matched DB-side against instance name, deployment id, revision hash
and initiating user. Search is TRAILING-edge debounced at 500ms — the same Timer +
lock + _disposed idiom as the existing leading-edge push coalescer, minus the leading
edge, because a search box must not query on the first keystroke. A filter change
resets to page 1; a page past the end falls back to the last real page. Bootstrap
only, existing PagerWindow pager retained.

The WP2.4 push coalescing is unchanged and still earns its keep: server paging shrank
what a reload costs, not how many arrive — it now bounds database round-trips rather
than table scans.

Tests: 19 new SQLite repository tests (paging slice + total, tie-break stability
across pages, page/size clamping, past-the-end, the joined projection, every filter
dimension incl. the empty-scope security case, and the grouped counts' status-blind
contract); 15 new bUnit page tests (page-1 request, server total drives the pager,
Next re-queries, tiles show server counts not page counts, tile filter + toggle,
system-wide vs site-scoped scope push, debounce collapses a keystroke burst to one
query, clear-filters, dispose with an armed timer). The two existing Deployments
suites re-point their reload assertions at the new query.

Doc: Component-CentralUI.md Deployment section — the "no server-side paging" known
residual is replaced by the shipped design.
2026-08-15 03:31:28 -04:00
Joseph Doherty 8c0b36b2fa perf(ui): shared KPI cache, live-cache-backed alarm summary, coalesced debug renders 2026-08-14 20:53:38 -04:00
Joseph Doherty 9e243493fb ui: Central UI density/consistency sweep + Theme 0.4.1
Applies the family-wide admin-UI cleanup playbook to the Central UI so the
Blazor surfaces stop diverging from the shared kit: buttons are grouped rather
than individually sized, long cell values are contained instead of widening
tables, and hard-coded colours give way to theme tokens.

The headline fix is that MainLayout passed Accent="#2f5fd0" to ThemeShell,
which the kit emits as an inline style on the shell root. Being a descendant of
<html>, it beat the [data-bs-theme="dark"] override for the entire app, so the
dark accent had never rendered. Declaring --accent in site.css :root instead
lets both schemes resolve; light is unchanged because the value already matched
the kit's light default.

Theme pins to 0.4.1, which upstreams the local .btn sizing block verbatim, so
that block is deleted here rather than duplicated. Verified byte-identical
before removal; the repo now declares no --bs-btn-* anywhere.

NOT purely cosmetic, contrary to the sweep's stated scope: four detail-modal
surfaces (NotificationReport, ConfigurationAuditLog, ParkedMessages,
SiteCallsReport) were additionally refactored from holding the selected record
to holding its id and re-resolving from the current page each render, with the
resolve doubling as the visibility gate. A background refresh that drops the
row now closes the modal instead of showing a stale snapshot. This is a
behaviour change and is called out rather than buried: a full-suite run turned
up one intermittent CentralUI failure, CloseButton_DismissesModal, whose stack
(GetRequiredEventBindingEntry during DispatchEventAsync) indicates the handler
was disposed between render and click — a window the previous field-held record
made structurally impossible. Treat the modal lifecycle here as unreviewed.

Build 0/0; suite green apart from that one intermittent failure.
2026-08-11 05:50:12 -04:00
Joseph Doherty 410349767d style(centralui): full-app bg-light/bg-white -> theme-aware utility sweep (M10 residual)
T34c only fixed the bounded modal-surface offenders. Bootstrap 5.3's bg-light
and bg-white are fixed light values that do NOT flip under [data-bs-theme=dark],
so every remaining use was a dark-mode contrast break. 35 swaps across 19 files:

  surface / <pre> / <code>  bg-light                -> bg-body-secondary
  panel                     bg-white                -> bg-body
  neutral badge             bg-light text-dark      -> bg-secondary-subtle text-secondary-emphasis
  muted badge / input group bg-light text-muted     -> bg-body-secondary text-body-secondary

DELIBERATELY LEFT (7 sites): the neutral member of a status-badge switch or
ternary whose siblings are all solid, non-theme-aware colours (bg-success,
bg-danger, bg-warning) — swapping only the neutral one to a subtle token breaks
the visual weight of the set, so these stay until the whole family is retoned:
  Topology.razor:513 (Current badge) and :588 (InstanceState.NotDeployed)
  InstanceConfigure.razor:1520 (same InstanceState switch)
  NotificationReport.razor StatusBadgeClass fallback
  TransportImport.razor ConflictKind badge fallback
  SecuredWrites.razor text-bg-light fallback (a text-bg-* family with no
    theme-aware member at all)
  Health.razor:377 depth ternary
Also untouched by design: SchemaBuilder.razor:88 (already bg-light-subtle,
theme-aware), bg-dark text-light console panels, and site.css / #reconnect-modal.

InstanceConfigure.razor is edited concurrently elsewhere; its change here is 5
pure class-string swaps on existing lines, no reflow. No test asserted any of
these classes. Also marks all four M10 residuals done in the plan doc.
2026-08-01 11:28:20 -04:00
Joseph Doherty a26d6ba317 feat(central-ui): native-alarm-source CSV import on InstanceConfigure
The Native Alarm Source Overrides card had no bulk affordance — the CSV path
shipped CLI-only (instance native-alarm-source import --file), so the UI could
only retarget one source at a time inline.

Adds a second InputFile on that card, mirroring the attribute importer's UX
(hidden input behind a button-styled label, 512 KB cap, success/error alert
with the per-line error list, toast). Parsing reuses the SHARED
NativeAlarmSourceOverrideCsvParser — the exact parser the CLI uses — and the
new pure InstanceConfigure.BuildNativeAlarmSourceCsvImport applies the same
batch rules the server enforces in
ManagementActor.HandleSetInstanceNativeAlarmSourceOverrides: the source must
resolve, must not be template-locked, and may appear at most once; any error
rejects the whole file and applies nothing.

Semantics match the CLI: merge, not full replace — sources absent from the
file keep their existing override, a blank field keeps the inherited value,
and an all-blank row clears that source's override (equivalent to the CLI's
all-null override row, without leaving a dead row behind).

Persistence reuses the inline editor's path: SaveNativeOverride's upsert body
is extracted to UpsertNativeOverrideCore (no SaveChangesAsync inside), so the
import commits the validated batch in one SaveChangesAsync — no new server
method, no duplicated parsing.

Tests: InstanceConfigureNativeAlarmCsvImportTests (9) — happy path, blank-field
inheritance, all-blank clear, merge semantics, unknown/locked/duplicate source
and parser-error rejection, plus structural pins on the InputFile wiring.
Docs: Component-CentralUI.md native-alarm-source card gains the import bullet.
2026-08-01 11:11:40 -04:00
Joseph Doherty 5a878b78d4 docs(xml): fill missing XML doc comments + strip task-tracking refs across src (fixdocs)
Add missing <summary>/<param>/<returns>/<typeparam> tags and switch
interface implementations to <inheritdoc/> across 106 files; strip
project bookkeeping identifiers (Task NN, #05-TNN, PLAN-04, StoreAndForward-0NN)
from shipped code comments while preserving the descriptive rationale.
Comment-only: zero code-logic lines changed; solution builds 0/0.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-10 08:23:56 -04:00
Joseph Doherty 9cff87fe85 docs(comments): strip internal task/milestone/bundle bookkeeping from code comments
Remove project bookkeeping citations from shipped code comments across the
solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/
issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels,
and C/D/K/S/T phase labels.

Comment text only — no code logic, string/log literals, or XML-doc structure
changed. Genuine descriptions are preserved (only the citation is stripped),
and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365,
UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker
TaskReferenceInComment / TrackingReferenceInComment checks plus targeted
grep passes; full solution builds clean, append-only guard tests pass.
2026-07-07 11:03:26 -04:00
Joseph Doherty 99254b71de perf(ui): topology page — staleness off the live loop + parallelized one-shot 2026-06-26 16:53:44 -04:00
Joseph Doherty b432c788c3 refactor(ui): dedup alarm-capable protocol predicate to AlarmCapableProtocols (#53) 2026-06-19 01:32:55 -04:00
Joseph Doherty 07b651d933 refactor(centralui): tokenize remaining standalone modal backdrops via .sb-modal-backdrop + bounded bg-* dark audit (T34c) 2026-06-18 20:04:17 -04:00
Joseph Doherty b7f7fe935c test(playwright): alarm-override trigger-config scenarios — HiLo merge, non-HiLo replace, validation, cancel, clear (T41) 2026-06-18 19:35:14 -04:00
Joseph Doherty d5e7e897c0 feat(centralui): InstanceConfigure CSV bulk override import (T16) 2026-06-18 02:30:33 -04:00
Joseph Doherty bf1f2f6892 feat(centralui): extract AlarmStateBadges shared component from DebugView (T13) 2026-06-18 02:02:09 -04:00
Joseph Doherty ef86a2db28 refactor(debugview): cosmetic polish — test-seam comment, default-arm comment, tighten severity assertion 2026-06-17 15:30:18 -04:00
Joseph Doherty 50ce26f2e6 feat(centralui): DV-5 — Debug View tabbed composition trees (Attributes/Alarms)
Replace the two flat capped tables with a Bootstrap nav-tabs layout, each
tab hosting a TreeView<DebugTreeNode> built from the live latest-per-name
dictionaries via DebugTreeBuilder. Drop the MaxRows cap, auto-scroll locks,
and Clear buttons (change-feed affordances that don't fit a current-status
tree); HandleStreamEvent now does a plain dictionary upsert. Per-tab filters
ExpandAll on change so matches stay visible. Branch nodes surface roll-up
badges (active-count for alarms, bad-quality for attributes); native binding
nodes show active-count or 'no active conditions'. All existing badge helpers
and ValueFormatter reused. Marshalling/dispose/reconnect contract preserved
(SafeInvokeAsync/_disposed/Dispose unchanged; FilteredAttributeValues kept as
the render-thread dict reader the CentralUI-021 race test exercises).

Rework DebugViewAlarmTableTests for the tabbed-tree DOM: tab presence+default,
computed alarm grouped under its Motor1 branch with the active roll-up badge,
and a native condition nested under its source-binding node with the enriched
kind/severity/Unacked/Shelved badge set.
2026-06-17 15:23:49 -04:00
Joseph Doherty c1e786e3fc refactor(DebugTreeBuilder): DRY BuildAttributeTree via WalkBranches; guard empty NativeSourceCanonicalName 2026-06-17 15:18:09 -04:00
Joseph Doherty 5f387ef3e3 feat(debugview): DV-4 implement BuildAlarmTree (computed leaves, native binding nodes, roll-up, filter)
Computed alarms place as leaves at their path-qualified AlarmName; native conditions group under a deduped IsNativeBinding branch keyed by NativeSourceCanonicalName with condition children keyed canonical::sourceRef. Configured-placeholder events materialise a childless binding node. Alarm roll-up (WorstState/ActiveCount) excludes placeholders. Filter matches AlarmName/SourceReference/NativeSourceCanonicalName (OrdinalIgnoreCase) and retains ancestor + binding branches. 20 new TDD cases; 18 attribute cases stay green. No DebugTreeNode model changes.
2026-06-17 15:12:57 -04:00
Joseph Doherty 69b83379d5 test(dv-3): add 4-level roll-up + deep-leaf filter tests; return AsReadOnly; add caller-contract remark
Fix 1 (Important): RollUp_FourLevelDeepBadQuality_ReachesRoot — proves bad quality at a
4-segment-deep leaf propagates HasBadQuality up every ancestor to the root.

Fix 2 (Important): Filter_DeepLeafMatch_RetainsAllAncestorBranches — proves filtering on
a terminal segment of a 3-level path retains all ancestor branches.

Fix 3 (Minor): BuildAttributeTree now returns roots.AsReadOnly() so the returned
IReadOnlyList<DebugTreeNode> reference is not a mutable list.

Fix 4 (Minor): Added <remarks> XML doc to BuildAttributeTree noting the caller-contract
that at most one AttributeValueChanged per AttributeName should be passed.

All 18 DebugTreeBuilder tests pass.
2026-06-17 15:09:01 -04:00
Joseph Doherty cc017aabfc feat(debugview): DV-3 DebugTreeNode model + attribute tree builder
Pure path-split composition forest from streamed AttributeValueChanged: branch dedupe by accumulated prefix, ordinal child sort, post-order bad-quality roll-up, case-insensitive name-contains filter (keeps ancestors). BuildAlarmTree left as a NotImplementedException stub for DV-4. 16 unit tests cover structure + roll-up + filter.
2026-06-17 15:01:02 -04:00
Joseph Doherty ca9ee5ea2a fix(ui): MV-14 review — surface SetAttributeOverride failures in InstanceConfigure save loop (no false success toast) 2026-06-16 16:32:28 -04:00
Joseph Doherty ae2e1efb1c feat(ui): List attribute override editor in InstanceConfigure
When overriding a List attribute, render the shared AttributeListEditor
(whole-list replacement; element type fixed by the base, shown read-only via
ShowElementType=false) instead of the single-line input. Loading an existing
override decodes its JSON into rows (malformed -> empty); saving encodes rows to
canonical JSON with a pre-submit Decode round-trip guard surfacing element
errors inline. Clearing removes the InstanceAttributeOverride row
(repository-direct, mirroring native-alarm-source overrides). Non-List override
UX unchanged.
2026-06-16 16:25:58 -04:00
Joseph Doherty d160c7f694 test(communication): M2.11 review nits — bridge-actor not-found test + dead-letter comment + toast wording (#24)
- Add DebugStreamBridgeActorTests: On_InstanceNotFound_Snapshot_Forwards_To_OnEvent_Does_Not_Open_Stream_And_Terminates — asserts _onEvent receives the not-found snapshot, SubscribeCalls remains empty, and the actor terminates cleanly via Watch/ExpectTerminated.
- Add comment in DebugStreamBridgeActor near Context.Stop(Self) explaining that the subsequent StopDebugStream Tell from DebugStreamService.StopStream produces a benign expected dead-letter.
- Reword not-found toast in DebugView.razor to "Instance not found on the selected site — check the deployment target." (accurate when the instance may be deployed to a different site).
2026-06-16 06:15:26 -04:00
Joseph Doherty dbf44b9e10 fix(siteruntime): M2.11 — unknown-instance debug snapshot returns InstanceNotFound=true (#24)
RouteDebugSnapshot and RouteDebugViewSubscribe on DeploymentManagerActor
previously returned an empty DebugViewSnapshot for unknown instances,
indistinguishable from a deployed-but-empty instance. Callers had no way
to differentiate "not deployed here" from "deployed, no data yet."

Approach — additive field on existing message contract:
  Added `bool InstanceNotFound = false` as an optional trailing parameter
  to DebugViewSnapshot (Commons). All existing positional constructor calls
  and serialized wire frames are unaffected (default = false). A dedicated
  new message type was considered but rejected: the ClusterClient channel
  and DebugStreamService TCS are already typed on DebugViewSnapshot, and a
  second reply union would require wider changes for zero additive-safety
  gain.

Changes:
  - Commons/DebugViewSnapshot: add InstanceNotFound = false (additive)
  - DeploymentManagerActor: set InstanceNotFound=true in both unknown-
    instance branches (RouteDebugViewSubscribe, RouteDebugSnapshot)
  - DebugStreamBridgeActor: when snapshot.InstanceNotFound, forward it to
    _onEvent (resolves the TCS) then stop cleanly; no gRPC stream opened
  - DebugView.razor: check session.InitialSnapshot.InstanceNotFound after
    connect and show a clear "not deployed on this site" error toast
  - 3 new tests in DeploymentManagerActorTests covering: unknown→snapshot,
    unknown→subscribe, known-empty→InstanceNotFound stays false
2026-06-16 06:08:21 -04:00
Joseph Doherty 198770f578 fix(deploy): address M2.2 review nits — backup endpoint in diff summary + null-oldConfig test (#10)
- FormatConnection now includes BackupConfigurationJson so a backup-only change
  no longer renders identical Before/After cells (covers all 4 ConnectionsEqual fields)
- add ComputeConnectionsDiff(null, newConfig) first-deploy unit test
2026-06-15 13:41:39 -04:00
Joseph Doherty e9a84ba220 feat(deploy): surface connection-level changes in the deployment diff (#10)
ComputeConnectionsDiff existed with tests but was never called and ConfigurationDiff
had no slot for it, so standalone connection endpoint/protocol/failover drift never
appeared in the deployment diff (only per-attribute binding drift did). Add a
ConnectionChanges slot, wire ComputeConnectionsDiff into ComputeDiff, and render the
connection section in the deployment diff UI.
2026-06-15 13:36:40 -04:00
Joseph Doherty bc8960779b feat(ui): add data-test hooks to InstanceConfigure alarm-override section 2026-06-07 10:10:50 -04:00
Joseph Doherty 137de66d20 feat(ui): add data-test hooks to DebugView site/instance selects (additive, test-only) 2026-06-06 13:22:32 -04:00
Joseph Doherty c23e2bf227 feat(centralui): add data-test hooks to InstanceConfigure selects + error alert (test instrumentation) 2026-06-06 11:37:03 -04:00
Joseph Doherty 046797e699 feat(ui): instance configure native alarm source override panel 2026-05-31 02:46:54 -04:00
Joseph Doherty 1f6c4207df feat(ui): enrich DebugView alarm table with severity + condition state + native metadata 2026-05-31 02:34:12 -04:00
Joseph Doherty 4881f9c23c fix(centralui): enable Test Bindings for MxGateway connections
The Test Bindings button was disabled (greyed out) for any attribute bound
to a non-OPC-UA connection. BuildTestableRows() filtered to protocol ==
"OpcUa", a stale gate left over from when OPC UA was the only protocol.
ReadTagValuesCommand is protocol-agnostic (routes through
IDataConnection.ReadBatchAsync, which MxGatewayDataConnection implements),
so the filter only blocked the UI — mirroring the already-fixed IsBrowsable.

Remove the OPC-UA-only filter and update the stale comments. Add a bUnit
regression test (theory over MxGateway + OpcUa) asserting the button is
enabled for a readable-protocol binding.

Verified live: dialog opens for an MxGateway binding and returns a
Good-quality read.
2026-05-29 12:26:46 -04:00
Joseph Doherty 569edf2975 feat(centralui): enable tag picker for MxGateway connections
Generalize the browse-button gate from IsOpcUa to IsBrowsable (OPC UA or
MxGateway, both implement IBrowsableDataConnection site-side). The generalized
NodeBrowserDialog + BrowseNodeCommand path already routes by protocol; non-
browsable protocols return NotBrowsable. Test Bindings stays OPC-UA-only (its
read path is out of this slice's scope).
2026-05-29 08:03:59 -04:00
Joseph Doherty cb0d17dabd refactor(browse): rename OPC-UA browse service + dialog to protocol-agnostic
IOpcUaBrowseService/OpcUaBrowseService -> IBrowseService/BrowseService,
OpcUaBrowserDialog -> NodeBrowserDialog, and neutralize 'Browse OPC UA' UI
strings to 'Browse'. Updates DI, InstanceConfigure, TestBindingsDialog, TreeRow,
BindingTester, and tests. 574 CentralUI tests green.
2026-05-29 07:59:56 -04:00
Joseph Doherty 2a7dee4afa feat(centralui+dcl): Test Bindings popup — one-shot live read of bound tags
Adds a Test Bindings button to the Connection Bindings table on the Configure
Instance page that opens a modal showing the live current value of every bound
attribute. Reuses the routing path that the OPC UA tag browser landed on:

  Central:  TestBindingsDialog → IBindingTester → CommunicationService
            → ReadTagValuesCommand → SiteEnvelope (Ask)
  Site:     SiteCommunicationActor → DeploymentManagerActor singleton
            → DataConnectionManagerActor → child DataConnectionActor
            → _adapter.ReadBatchAsync

Split mirrors the browse handler:
  • Manager owns ConnectionNotFound (only it sees the per-site connection set).
  • Child owns ConnectionNotConnected (pre-call status check, never stash —
    read is interactive design-time), Timeout (OperationCanceledException),
    ServerError (any other exception). Per-tag failures from ReadBatchAsync
    become failure TagReadOutcomes without aborting the batch.

CentralUI:
  • IBindingTester / BindingTester — Design-role guard via HasClaim against
    JwtTokenService.RoleClaimType (not IsInRole — see c1e16cf), typed
    transport-failure translation.
  • TestBindingsDialog — ShowAsync(siteId, rows, instanceLabel) method-arg
    pattern (no Razor parameter race; see 2c138b6), groups rows by connection
    and issues one ReadAsync per connection in parallel, per-row error subline
    + per-connection banner, Refresh button re-issues the reads.
  • InstanceConfigure.razor — Test Bindings button next to Save Bindings,
    disabled when no testable rows. OPC UA only today (other protocols have
    no ReadTagValuesCommand wiring yet).

Tests:
  • Commons: ReadTagValuesCommand discovered by ManagementCommandRegistry.
  • DataConnectionLayer: unknown connection → ConnectionNotFound,
    not-connected adapter → ConnectionNotConnected (ReadBatchAsync NOT called),
    success-path mapping (Good/Bad + per-tag error), cancellation → Timeout.
  • CentralUI: register IBindingTester (and the previously-missing
    IOpcUaBrowseService) on the existing InstanceConfigureAuditDrillinTests
    Bunit container so the page renders cleanly with the new dialog.
2026-05-28 13:25:48 -04:00
Joseph Doherty 2c138b6a25 fix(centralui): pass siteId+connectionName into ShowAsync explicitly
Razor parameter binding propagates on the next render, so reading SiteId
inside LoadRootAsync raced against the parent's "set field, then call
ShowAsync()" pattern — central received an empty siteId and rejected
with "No ClusterClient for site ,". Take the values as args instead.
2026-05-28 12:40:35 -04:00
Joseph Doherty 3162370a8f feat(centralui): add OPC UA browse button + override column to InstanceConfigure 2026-05-28 12:14:26 -04:00
Joseph Doherty 7b0b9c7365 refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
2026-05-28 09:37:45 -04:00