Commit Graph

1362 Commits

Author SHA1 Message Date
Joseph Doherty 846cc5d96d fix(dcl): route native OPC UA alarms by binding identity, not event name
A native OPC UA alarm source's SourceReference has to be two things at once:
it is parsed as a NodeId to open the monitored item, and matched as a plain-name
prefix against the event's SourceName to route the transition to an instance. No
string is both, so a NodeId-form binding subscribed correctly and then silently
dropped every transition — "pymodbus/plc/HR200".StartsWith("nsu=...;s=pymodbus/plc/HR200")
is false. Only the empty (Server-object) binding worked, because StartsWith("")
matches everything, which is why the sole live smoke test never caught it.

Each OPC UA alarm feed is opened for exactly one binding, so every transition on
it belongs to that binding. The adapter now tags each transition's routing
identity (SourceObjectReference) with the binding string verbatim via the pure
OpcUaAlarmMapper.BuildIdentity, making DataConnectionActor's routing key an exact
match regardless of whether the binding is stored as ns=<index> or the durable
nsu=<uri> form. The Server-object aggregate feed keeps an empty routing identity,
so it reaches only "mirror everything" subscribers and never leaks into a
specific-node binding. The per-condition SourceReference key stays the readable
SourceName.ConditionName, so persistence and display are unchanged, and MxGateway
is untouched — its bindings are names and its mapper already emits matching names.

Unblocked by lmxopcua#473 (OtOpcUa now populates SourceNode/SourceName/EventType
on conditions); SourceName is the RawPath, so the per-condition key is unique.
Live end-to-end verification against native alarms still needs a v3 rig.

Fixes: Gitea #17
2026-07-17 14:50:50 -04:00
Joseph Doherty d32050da24 feat(dcl): bind OPC UA nodes by namespace URI, not baked index
A stored ns=<index> reference is only meaningful against one server's namespace
table. A server that adds, removes or reorders a namespace silently re-points
every binding: best case BadNodeIdUnknown, worst case the index now names a
different namespace holding a colliding identifier and the binding resolves to
the wrong node with Good quality. Nothing could detect that, because ScadaBridge
stored no namespace URI anywhere.

OtOpcUa v3.0 makes this concrete: v2's sole custom namespace and v3's raw
namespace both sit at index 2, so v2-era bindings resolve against v3 without
error while meaning something else entirely.

Adds OpcUaNodeReference as the single translation seam between stored references
and the wire. Resolve() accepts both ns=<index>;s=<id> (existing bindings, which
keep working unchanged) and the durable nsu=<uri>;s=<id>, mapping the URI to the
live index at use time; it is wired into every parse site — subscribe, read,
write, alarm-subscribe and browse. An unpublished URI now throws naming the URI
rather than binding to whatever occupies that index, and a svr= reference to
another server is rejected instead of being resolved against the wrong address
space.

The browser emits ToDurable(), so what the picker shows is what gets stored and
newly-authored bindings are index-proof from the start. That also closes a
round-trip gap: browse previously emitted ExpandedNodeId.ToString(), which for a
URI- or server-index-carrying reference produced a string NodeId.Parse could not
read back — the same method already resolved it correctly 57 lines later.

Bindings stored before this change keep their ns= form and keep working; they are
only as durable as the server's namespace order. Re-authoring against the picker
is what makes them durable, and that re-bind still needs a live v3 rig.

Refs: Gitea #14
2026-07-17 14:50:50 -04:00
Joseph Doherty 128f159692 feat(secrets): resolve MxGateway ApiKey secret: refs at connect time (ScadaBridge G-3 T9) 2026-07-16 15:37:01 -04:00
Joseph Doherty 56b428d2ef docs(secrets): clustered master-key posture runbook + Central pointer (ScadaBridge G-5 T8) 2026-07-16 15:21:54 -04:00
Joseph Doherty 41e17d2ff8 feat(secrets): mount /admin/secrets + register secrets authz in CentralUI (ScadaBridge G-6 T7) 2026-07-16 15:09:34 -04:00
Joseph Doherty cf715b813b feat(secrets): switch Central config secrets to ${secret:} tokens (ScadaBridge G-4 T4)
Switch the three literal ${SCADABRIDGE_*} placeholders in appsettings.Central.json
to ${secret:} references resolved by the pre-host secrets expander, and update the
_secrets doc note. Fix Host.Tests fixtures that boot the real Program pipeline against
Central config: CentralDbTestEnvironment now also supplies the LDAP service-account
password and JWT signing key as whole-key env overrides (rollback path) so the expander
skips the tokens without a seeded store; the three fixtures that managed env vars
inline now use CentralDbTestEnvironment.
2026-07-16 14:42:28 -04:00
Joseph Doherty abc9c96af0 feat(secrets): register runtime AddZbSecrets on Central + Site roles (ScadaBridge T3) 2026-07-16 14:26:16 -04:00
Joseph Doherty ae62e650e9 feat(secrets): pre-host ${secret:} config expander (ScadaBridge G-4 T2) 2026-07-16 14:13:53 -04:00
Joseph Doherty 481d6d7265 build(secrets): add ZB.MOM.WW.Secrets 0.1.2 package refs (ScadaBridge G-3/4/5/6 T1) 2026-07-16 14:09:44 -04:00
Joseph Doherty ccee558ca7 merge(r2): r2-plan08
# Conflicts:
#	src/ZB.MOM.WW.ScadaBridge.Communication/CommunicationOptionsValidator.cs
2026-07-13 11:10:15 -04:00
Joseph Doherty ca4b0dd849 merge(r2): r2-plan04 2026-07-13 11:09:36 -04:00
Joseph Doherty 699af8fc16 merge(r2): r2-plan06
# Conflicts:
#	CLAUDE.md
2026-07-13 11:09:24 -04:00
Joseph Doherty 84fdea60cf merge(r2): r2-plan05 2026-07-13 11:08:27 -04:00
Joseph Doherty 3ad5ad0d7e merge(r2): r2-plan03 2026-07-13 11:08:27 -04:00
Joseph Doherty 774e3918db merge(r2): PLAN-R2-07 UI/Management/Security 2026-07-13 11:08:10 -04:00
Joseph Doherty 2751a6dba9 test(management): freeze scrubber fragment coverage over DataConnection config types + document array-merge limit (plan R2-07 T12) 2026-07-13 11:05:19 -04:00
Joseph Doherty 1b88ca1296 fix(security): amortize LoginThrottle.Prune off the failed-bind hot path (plan R2-07 T11) 2026-07-13 11:03:26 -04:00
Joseph Doherty 5b16429635 fix(sitestream): deathwatch resets live-cache liveness on aggregator termination (plan R2-07 T10) 2026-07-13 11:01:54 -04:00
Joseph Doherty 56d39b5fce fix(host): site singletons (deployment-manager, event-log-handler) via SingletonRegistrar — PhaseClusterLeave drains for site SQLite writes (plan R2-01 T11) 2026-07-13 11:01:44 -04:00
Joseph Doherty 80b967681e chore(hygiene): fix stale area-export comment in EntitySerializer.FromBundleContent — areas travel by name since PLAN-05 (plan R2-08 T12, arch-review 08r2 NF6) 2026-07-13 10:58:52 -04:00
Joseph Doherty 76ef97f729 fix(ui): disposal guard on Alarm Summary live callback, matching the DebugView pattern (plan R2-07 T9) 2026-07-13 10:58:50 -04:00
Joseph Doherty 7943d73e71 fix(options): canonicalize Communication/DataConnection config sections to ScadaBridge:*; drop the duplicate Host bindings that were masking the drift (plan R2-08 T9, arch-review 08r2 NF8) 2026-07-13 10:58:28 -04:00
Joseph Doherty 925c869826 fix(ui): Alarm Summary poll defers row rebuilds to the live path while IsLive (plan R2-07 T8) 2026-07-13 10:57:09 -04:00
Joseph Doherty d33337a834 fix(ui): drop stale-site poll results on Alarm Summary site switch (plan R2-07 T7) 2026-07-13 10:55:25 -04:00
Joseph Doherty 973e59de84 fix(options): bind + validate OperationTrackingOptions; register the site IOperationTrackingStore that two comments claimed AddSiteRuntime provides (plan R2-08 T8, arch-review 08r2 NF4)
VERIFY-THEN-FIX: the plan-authoring discovery is CONFIRMED. No code anywhere in
src/ registered IOperationTrackingStore in DI, yet AkkaHostedService and
AuditLog SCE both comment that AddSiteRuntime provides it. Every consumer
resolved it via GetService (null-tolerant) and silently ran degraded:
cached-drain scheduler never armed, PullSiteCalls reconciliation seam never
wired, Tracking.Status degraded to audit-only. This is a FUNCTIONAL FIX riding
the hygiene plan: AddSiteRuntime now registers the store (site-only), so
site-local cached-call tracking runs in its intended mode. OperationTrackingOptions
is now bound + eagerly validated in the Host site-options block.
2026-07-13 10:53:27 -04:00
Joseph Doherty 1fcfa4fb16 fix(security): scope-filter secured-write listing + align spec with enforced site scoping (plan R2-07 T6) 2026-07-13 10:53:16 -04:00
Joseph Doherty 80e5b36852 refactor(host): CentralSingletonRegistrar -> SingletonRegistrar with optional role scope — unblocks site-singleton drains (plan R2-01 T10) 2026-07-13 10:49:49 -04:00
Joseph Doherty 4a0462e4d0 fix(options): validate Host Node/Database/Logging options at startup — empty NodeName fails fast instead of NULLing SourceNode (plan R2-08 T7, arch-review 08r2 NF4) 2026-07-13 10:49:42 -04:00
Joseph Doherty 92936747b7 fix(security): additive permitted-sites filter on secured-write query/count (plan R2-07 T5) 2026-07-13 10:48:45 -04:00
Joseph Doherty c2707fdcf8 docs(health): CentralHealthReportLoop comments say oldest-member, not cluster leader — stop re-teaching the S2 conflation (plan R2-01 T9) 2026-07-13 10:46:18 -04:00
Joseph Doherty 9b9222d223 fix(security): enforce LDAP-mapping site scope on secured-write submit/approve/reject (plan R2-07 T4) 2026-07-13 10:46:08 -04:00
Joseph Doherty 11efe4be05 fix(health): flag metrics-stale for never-reported sites via FirstSeenAt anchor — null LastReportReceivedAt no longer skips the check (plan R2-01 T8) 2026-07-13 10:45:22 -04:00
Joseph Doherty f85f036b09 fix(options): eager startup validation for AuditLog central sub-options — PartitionMaintenance, Purge, Reconciliation (plan R2-08 T6, arch-review 08r2 NF4) 2026-07-13 10:39:03 -04:00
Joseph Doherty b532a1e30e fix(security): trusted-proxy ForwardedHeaders so LoginThrottle keys on the real client IP (plan R2-07 T2) 2026-07-13 10:38:41 -04:00
Joseph Doherty e0dddac0be fix(options): eager startup validation for AuditLog site sub-options — SiteWriter, SiteTelemetry, SiteRetention (plan R2-08 T5, arch-review 08r2 NF4) 2026-07-13 10:36:46 -04:00
Joseph Doherty e1a692016e fix(security): throttle DebugStreamHub LDAP bind via ManagementAuthenticator + correct coverage doc (plan R2-07 T1) 2026-07-13 10:36:32 -04:00
Joseph Doherty c573d8cac6 fix(audit-log): retention loop absorbs shutdown cancellation instead of faulting host StopAsync (plan R2-04 T12) 2026-07-13 10:28:03 -04:00
Joseph Doherty c83b51fe9c fix(comm): single-gRPC-endpoint sites can go live (flip degenerates to same-node reconnect) (plan R2-02 T15) 2026-07-13 10:26:43 -04:00
Joseph Doherty 8f46b6cec2 fix(sitecallaudit): time-sliced terminal purge replaces the single year-scale DELETE (plan R2-04 T11) 2026-07-13 10:26:33 -04:00
Joseph Doherty 41ffe42de3 docs(comm): accept + document standby aggregators and deleted-site viewer behavior; fix aggregator e2e ctor call for new params (plan R2-02 T14) 2026-07-13 10:25:10 -04:00
Joseph Doherty 3d6973cc89 perf(sitecallaudit): filtered IX_SiteCalls_Terminal index backs the retention purge predicate (plan R2-04 T10) 2026-07-13 10:23:19 -04:00
Joseph Doherty 4e228110ec fix(comm): site delete disposes the site's cached gRPC channels via RemoveSiteAsync (plan R2-02 T13) 2026-07-13 10:22:56 -04:00
Joseph Doherty 439dee52d3 fix(kpi): classify the failover fold-race pass loss as a self-healing Information event (plan R2-04 T9) 2026-07-13 10:21:02 -04:00
Joseph Doherty b7f8632db8 chore(comm): ctor-inject aggregator reconnect/stability tuning, remove process-global statics (plan R2-02 T12) 2026-07-13 10:19:51 -04:00
Joseph Doherty c29e588905 fix(comm): queue failover re-seed behind in-flight fan-out; stamp stream generation on gRPC errors (plan R2-02 T11) 2026-07-13 10:18:16 -04:00
Joseph Doherty cd93ad3976 fix(kpi): promote catalog metric literals to shared KpiMetrics constants — drift becomes a compile error (plan R2-04 T8) 2026-07-13 10:15:40 -04:00
Joseph Doherty bb8be55382 perf(comm): coalesce live-alarm delta publishes (250ms window, 0 = legacy) (plan R2-02 T10) 2026-07-13 10:14:54 -04:00
Joseph Doherty 1c8b2bc745 docs(kpi): trend charts state per-bucket-total semantics for rate metrics (plan R2-04 T7) 2026-07-13 10:12:58 -04:00
Joseph Doherty cc4ff7029d chore(saf): eagerly validate SweepBatchLimit/SweepTargetParallelism (plan R2-02 T9) 2026-07-13 10:10:34 -04:00
Joseph Doherty 9a1b160e79 fix(kpi): query service applies catalog aggregation at the bucketer — rate charts keep one unit across the raw/rollup boundary (plan R2-04 T6) 2026-07-13 10:10:30 -04:00