diff --git a/README.md b/README.md index b75ef691..a3b6e59e 100644 --- a/README.md +++ b/README.md @@ -115,5 +115,47 @@ Both stacks share the infrastructure services in [`infra/`](infra/) (MS SQL, LDA ### Site Runtime Actor Hierarchy -![Site runtime actor hierarchy](diagrams/site-runtime-actor-hierarchy.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + DMS["Deployment Manager Singleton
(Cluster Singleton)"] + IA["Instance Actor
(one per deployed, enabled instance)"] + IA2["Instance Actor
( … )"] + MOREIA["… more Instance Actors"] + DMS --> IA + DMS --> IA2 + DMS -.-> MOREIA + + SA["Script Actor
(coordinator, one per instance script)"] + AA["Alarm Actor
(coordinator, one per alarm definition)"] + MORE1["… more Script /
Alarm Actors"] + IA --> SA + IA --> AA + IA -.-> MORE1 + + SEA["Script Execution Actor
(short-lived, per invocation)"] + AEA["Alarm Execution Actor
(short-lived, per on-trigger invocation)"] + IA2C["… (Script / Alarm Actors)"] + SA --> SEA + AA --> AEA + IA2 -.-> IA2C + + subgraph STREAM["Site-Wide Akka Stream"] + PUB["All Instance Actors"] + STR["Site-Wide Akka Stream
(attribute + alarm state changes)"] + DBG["Debug view
(instance-level filtering)"] + PUB -->|publish| STR + STR -->|subscribe filtered| DBG + end + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class DMS,STR alt + class IA,IA2,PUB proc + class SA,AA,DBG start + class SEA,AEA warn + class MOREIA,MORE1,IA2C muted +``` diff --git a/diagrams/site-runtime-actor-hierarchy.drawio b/diagrams/site-runtime-actor-hierarchy.drawio deleted file mode 100644 index e2da42a3..00000000 --- a/diagrams/site-runtime-actor-hierarchy.drawio +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/diagrams/site-runtime-actor-hierarchy.png b/diagrams/site-runtime-actor-hierarchy.png deleted file mode 100644 index 1159aa07..00000000 Binary files a/diagrams/site-runtime-actor-hierarchy.png and /dev/null differ diff --git a/docs/deployment/diagrams/topology-architecture-overview.drawio b/docs/deployment/diagrams/topology-architecture-overview.drawio deleted file mode 100644 index 5b923205..00000000 --- a/docs/deployment/diagrams/topology-architecture-overview.drawio +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/deployment/diagrams/topology-architecture-overview.png b/docs/deployment/diagrams/topology-architecture-overview.png deleted file mode 100644 index 85163317..00000000 Binary files a/docs/deployment/diagrams/topology-architecture-overview.png and /dev/null differ diff --git a/docs/deployment/topology-guide.md b/docs/deployment/topology-guide.md index 4452ed7f..532e0843 100644 --- a/docs/deployment/topology-guide.md +++ b/docs/deployment/topology-guide.md @@ -6,8 +6,51 @@ ScadaBridge uses a hub-and-spoke architecture: - **Central Cluster**: Two-node active/standby Akka.NET cluster for management, UI, and coordination. - **Site Clusters**: Two-node active/standby Akka.NET clusters at each remote site for data collection and local processing. -![topology-architecture-overview](diagrams/topology-architecture-overview.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + USERS["Users
(HTTPS / LB)"] + + subgraph CENTRAL["Central Cluster"] + NA["Node A
Active"] + NB["Node B
Standby"] + NA <--> NB + end + + USERS --> NA + CENTRAL --> SITE01 + CENTRAL --> SITE02 + CENTRAL --> SITE03 + CENTRAL --> SITEN + + subgraph SITE01["Site 01"] + S01A["A
Active"] + S01B["B
Standby"] + end + subgraph SITE02["Site 02"] + S02A["A
Active"] + S02B["B
Standby"] + end + subgraph SITE03["Site 03"] + S03A["A
Active"] + S03B["B
Standby"] + end + subgraph SITEN["Site N"] + SNA["A
Active"] + SNB["B
Standby"] + end + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class USERS dec + class CENTRAL proc + class NA,S01A,S02A,S03A,SNA start + class NB,S01B,S02B,S03B,SNB muted + class SITE01,SITE02,SITE03,SITEN warn +``` ## Central Cluster Setup diff --git a/docs/plans/2026-03-22-primary-backup-data-connections-design.md b/docs/plans/2026-03-22-primary-backup-data-connections-design.md index a921da38..ae1be25f 100644 --- a/docs/plans/2026-03-22-primary-backup-data-connections-design.md +++ b/docs/plans/2026-03-22-primary-backup-data-connections-design.md @@ -39,8 +39,43 @@ Both endpoints use the same `Protocol`. EF Core migration renames `Configuration The `DataConnectionActor` Reconnecting state is extended: -![primary-backup-failover-state-machine](diagrams/primary-backup-failover-state-machine.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + C(["Connected"]) + BQ["Push bad quality
to all subscribers"] + RT["Retry active endpoint
(5s interval)"] + INC["_consecutiveFailures++"] + BR{"Evaluate
_consecutiveFailures"} + SAME["Retry same endpoint"] + FO["Failover
- dispose adapter, switch _activeEndpoint, reset counter
- create fresh adapter with other config
- attempt connect"] + NB["Keep retrying indefinitely
(current behavior)"] + RC(["On successful reconnect (either endpoint)
1. Reset _consecutiveFailures = 0
2. ReSubscribeAll() — re-create subscriptions on new adapter
3. Transition to Connected
4. Log failover event if endpoint changed
5. Report active endpoint in health metrics"]) + + C -->|"disconnect detected"| BQ + BQ --> RT + RT -->|"failure"| INC + INC --> BR + BR -->|"< FailoverRetryCount"| SAME + SAME -.->|"retry"| RT + BR -->|">= FailoverRetryCount AND backup exists"| FO + BR -->|">= FailoverRetryCount AND no backup"| NB + NB -.->|"retry (round-robin n/a)"| RT + FO -->|"connect succeeds"| RC + FO -.->|"connect fails (round-robin: primary to backup to primary...)"| RT + RC -->|"Transition to Connected"| C + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef bad fill:#f8cecc,stroke:#b85450,color:#111111; + class C,RC start + class BQ,RT,SAME proc + class INC,BR dec + class FO warn + class NB bad +``` **On successful reconnect (either endpoint):** 1. Reset `_consecutiveFailures = 0` diff --git a/docs/plans/2026-05-12-opcua-config-model-design.md b/docs/plans/2026-05-12-opcua-config-model-design.md index aa877234..e73e12ce 100644 --- a/docs/plans/2026-05-12-opcua-config-model-design.md +++ b/docs/plans/2026-05-12-opcua-config-model-design.md @@ -32,8 +32,46 @@ We want a strongly-typed model for OPC UA endpoint configuration, a validator th ## Architecture -![opcua-config-model-architecture](diagrams/opcua-config-model-architecture.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + subgraph COMMONS["ZB.MOM.WW.ScadaBridge.Commons"] + TYPES["Types/DataConnections/
OpcUaEndpointConfig.cs (POCO)
OpcUaHeartbeatConfig.cs (POCO)
OpcUaSecurityMode.cs (enum)"] + VALID["Validators/
OpcUaEndpointConfigValidator.cs"] + SER["Serialization/
OpcUaEndpointConfigSerializer.cs"] + TYPES ~~~ VALID ~~~ SER + end + + subgraph CENTRALUI["ZB.MOM.WW.ScadaBridge.CentralUI"] + CUIFORMS["Components/Forms/
OpcUaEndpointEditor.razor (shared)"] + CUIPAGES["Pages/Admin/
DataConnectionForm.razor"] + CUIFORMS ~~~ CUIPAGES + end + + subgraph SITERUNTIME["ZB.MOM.WW.ScadaBridge.SiteRuntime"] + SRACTORS["Actors/
DeploymentManagerActor
(passes raw JSON to DataConnectionFactory)"] + SRDC["DataConnections.OpcUa/
OpcUaDataConnection.cs
(consumes typed model)"] + SRACTORS ~~~ SRDC + end + + COMMONS -->|referenced by| CENTRALUI + COMMONS -->|referenced by| SITERUNTIME + + NOTE["Both sides deserialize DataConnection.PrimaryConfiguration / BackupConfiguration
into the same OpcUaEndpointConfig instance. The DB column type does not change."] + CENTRALUI -.- NOTE + SITERUNTIME -.- NOTE + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class COMMONS dec + class TYPES,VALID,SER warn + class CENTRALUI,CUIFORMS,CUIPAGES proc + class SITERUNTIME,SRACTORS,SRDC start + class NOTE muted +``` Both sides deserialize from `DataConnection.PrimaryConfiguration` / `BackupConfiguration` strings into the same `OpcUaEndpointConfig` instance. The DB column type does not change. diff --git a/docs/plans/2026-05-24-second-environment.md b/docs/plans/2026-05-24-second-environment.md index ac99d799..e863019b 100644 --- a/docs/plans/2026-05-24-second-environment.md +++ b/docs/plans/2026-05-24-second-environment.md @@ -14,8 +14,44 @@ ## Task Dependency Graph -![env2-task-dependency-graph](diagrams/env2-task-dependency-graph.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart LR + GRP["all independent, all parallelizable, all ready from the start"] + T0["T0"] + T1["T1"] + T2["T2"] + T3["T3"] + T4["T4"] + T6["T6"] + T7["T7"] + T8["T8"] + T9["T9"] + T5["T5
lifecycle scripts"] + T10(["T10
manual smoke test"]) + NOTE["T10 is the only task that requires all of T0–T9 done. Everything else runs in parallel."] + + T0 --> T10 + T1 --> T10 + T2 --> T10 + T3 --> T10 + T6 --> T10 + T7 --> T10 + T8 --> T10 + T9 --> T10 + T0 --> T5 + T4 --> T5 + T5 --> T10 + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class T0,T1,T2,T3,T4,T6,T7,T8,T9 proc + class T5 warn + class T10 start + class GRP,NOTE muted +``` T10 is the only task that requires all of T0–T9 done. Everything else can run in parallel. diff --git a/docs/plans/2026-05-28-opcua-tag-browser-design.md b/docs/plans/2026-05-28-opcua-tag-browser-design.md index 32b23eec..2831da2e 100644 --- a/docs/plans/2026-05-28-opcua-tag-browser-design.md +++ b/docs/plans/2026-05-28-opcua-tag-browser-design.md @@ -18,8 +18,33 @@ ## Section 1 — Architecture -![opcua-tag-browser-architecture](diagrams/opcua-tag-browser-architecture.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + N1["Blazor Server browser"] + N2["CentralUI: InstanceConfigure.razor"] + N3["CentralUI: OpcUaBrowserDialog component"] + N4["CentralUI: IOpcUaBrowseService"] + N5["CommunicationService.SendCommandToSiteAsync of BrowseOpcUaNodeResult (siteId, BrowseOpcUaNodeCommand)"] + N6["Site: CentralCommunicationActor → DataConnectionManagerActor"] + N7["OPC UA server"] + + N1 -->|SignalR| N2 + N2 -->|opens| N3 + N3 -->|uses| N4 + N4 -->|implementation calls| N5 + N5 -->|"ClusterClient Ask
ManagementEnvelope { User, Command, CorrelationId }"| N6 + N6 -->|"dispatches to IBrowsableDataConnection (RealOpcUaClient)
OPC Foundation .NET SDK Browse service"| N7 + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + class N1,N2,N3,N4 proc + class N5 alt + class N6 start + class N7 warn +``` Three slices, top-to-bottom: 1. **Data model.** Add a per-instance OPC UA address override (new column on `InstanceConnectionBinding`). @@ -145,8 +170,48 @@ Returning failure inside `BrowseOpcUaNodeResult` (rather than exceptions across **Wire flow.** -![opcua-tag-browser-command-flow](diagrams/opcua-tag-browser-command-flow.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + S1["CentralUI.OpcUaBrowseService.BrowseChildrenAsync(siteId, connId, parent)"] + S2["CommunicationService.SendCommandToSiteAsync of BrowseOpcUaNodeResult (siteId, new BrowseOpcUaNodeCommand(connId, parent))"] + S3["ManagementEnvelope { User, Command, CorrelationId }
over ClusterClient"] + S4["Site: CentralCommunicationActor unwraps envelope"] + S5["Site: DataConnectionManagerActor receives BrowseOpcUaNodeCommand
(DCL coordinator actor — owns the per-connection IDataConnection instances)"] + + S1 --> S2 --> S3 --> S4 --> S5 + + subgraph HANDLER["Handler logic"] + direction TB + HL["Look up IDataConnection by Id"] + HNF["if not found → ConnectionNotFound"] + HNB["if not (conn is IBrowsableDataConnection) → NotBrowsable"] + HAW["else await conn.BrowseChildrenAsync(ParentNodeId, ct)"] + HNC["Catch ConnectionNotConnectedException → ConnectionNotConnected"] + HCN["Catch OperationCanceledException → Timeout"] + HSVC["Catch ServiceResultException → ServerError + verbatim msg"] + HSUC["Else success: BrowseOpcUaNodeResult(children, truncated, null)"] + HL --- HNF --- HNB --- HAW --- HNC --- HCN --- HSVC --- HSUC + end + + S5 -->|processes| HANDLER + + R1["Reply travels back via
CentralCommunicationActor → CommunicationService"] + R2["returned to CentralUI page"] + HANDLER -->|result / failure| R1 + R1 --> R2 + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef bad fill:#f8cecc,stroke:#b85450,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + class S1,R1,R2 proc + class S2,S3 alt + class S4,S5,HSUC start + class HANDLER,HL,HAW dec + class HNF,HNB,HNC,HCN,HSVC bad +``` Handler lives in the **DCL coordinator actor** (the same actor that owns the per-connection `IDataConnection` instances) — keeps lifecycle and browse co-located so we don't race against reconnect. diff --git a/docs/plans/2026-05-29-native-alarms.md b/docs/plans/2026-05-29-native-alarms.md index ae6038ac..e8696b27 100644 --- a/docs/plans/2026-05-29-native-alarms.md +++ b/docs/plans/2026-05-29-native-alarms.md @@ -16,8 +16,87 @@ ## Task dependency overview -![native-alarms-task-dependency](diagrams/native-alarms-task-dependency.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart LR + T1["T1"] + T3["T3"] + T2["T2"] + T10["T10
DCL actor"] + T11["T11
OPC UA adapter"] + T12["T12
MxGateway adapter"] + T17["T17
computed AlarmActor enrich"] + T18["T18
proto"] + T19["T19
grpc mapping"] + T23["T23
DebugView"] + + T4["T4"] + T5["T5"] + T6["T6"] + T7["T7
migration"] + T8["T8"] + T9["T9
validation"] + T20["T20"] + T21["T21
mgmt handlers"] + T26["T26
seed"] + T22["T22
CLI"] + T24["T24
template UI"] + T25["T25
instance UI"] + + T13["T13"] + T14["T14"] + T15["T15
NativeAlarmActor"] + T16["T16
InstanceActor wiring"] + + T15IN["inputs to T15:
T1, T2, T3, T4 (Resolved), T13, T14"] + T27["T27
docs"] + T28["T28
integration / manual verify"] + EVT["(everything) emits to T27 and T28"] + + T1 --> T2 + T1 --> T10 + T1 --> T11 + T1 --> T12 + T3 --> T2 + T3 --> T10 + T3 --> T11 + T3 --> T12 + + T2 --> T17 + T2 --> T18 + T18 --> T19 + T19 --> T23 + + T4 --> T5 + T4 --> T7 + T4 --> T8 + T4 --> T20 + T5 --> T6 + T6 --> T21 + T8 --> T9 + T20 --> T21 + T20 --> T22 + T20 --> T24 + T20 --> T25 + T21 --> T26 + + T13 --> T15 + T14 --> T15 + T15 --> T16 + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class T1,T2,T3,T10,T11,T12 proc + class T17,T18,T19,T23 alt + class T4,T5,T6,T7,T8,T9,T20,T21,T22,T24,T25,T26 start + class T13,T14 dec + class T15,T16 warn + class T27,T28,T15IN,EVT muted +``` --- diff --git a/docs/plans/diagrams/env2-task-dependency-graph.drawio b/docs/plans/diagrams/env2-task-dependency-graph.drawio deleted file mode 100644 index b6ec6e99..00000000 --- a/docs/plans/diagrams/env2-task-dependency-graph.drawio +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/env2-task-dependency-graph.png b/docs/plans/diagrams/env2-task-dependency-graph.png deleted file mode 100644 index 37efac82..00000000 Binary files a/docs/plans/diagrams/env2-task-dependency-graph.png and /dev/null differ diff --git a/docs/plans/diagrams/generate-plans-per-work-package.drawio b/docs/plans/diagrams/generate-plans-per-work-package.drawio deleted file mode 100644 index a6cc5f76..00000000 --- a/docs/plans/diagrams/generate-plans-per-work-package.drawio +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/generate-plans-per-work-package.png b/docs/plans/diagrams/generate-plans-per-work-package.png deleted file mode 100644 index bfcf4fce..00000000 Binary files a/docs/plans/diagrams/generate-plans-per-work-package.png and /dev/null differ diff --git a/docs/plans/diagrams/grpc-channel-bridging.drawio b/docs/plans/diagrams/grpc-channel-bridging.drawio deleted file mode 100644 index 23f1aafc..00000000 --- a/docs/plans/diagrams/grpc-channel-bridging.drawio +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/grpc-channel-bridging.png b/docs/plans/diagrams/grpc-channel-bridging.png deleted file mode 100644 index 42280836..00000000 Binary files a/docs/plans/diagrams/grpc-channel-bridging.png and /dev/null differ diff --git a/docs/plans/diagrams/grpc-reconnection-state-machine.drawio b/docs/plans/diagrams/grpc-reconnection-state-machine.drawio deleted file mode 100644 index 6393b5f8..00000000 --- a/docs/plans/diagrams/grpc-reconnection-state-machine.drawio +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/grpc-reconnection-state-machine.png b/docs/plans/diagrams/grpc-reconnection-state-machine.png deleted file mode 100644 index 578abe3c..00000000 Binary files a/docs/plans/diagrams/grpc-reconnection-state-machine.png and /dev/null differ diff --git a/docs/plans/diagrams/grpc-streams-architecture.drawio b/docs/plans/diagrams/grpc-streams-architecture.drawio deleted file mode 100644 index a18bba91..00000000 --- a/docs/plans/diagrams/grpc-streams-architecture.drawio +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/grpc-streams-architecture.png b/docs/plans/diagrams/grpc-streams-architecture.png deleted file mode 100644 index 3a5d663f..00000000 Binary files a/docs/plans/diagrams/grpc-streams-architecture.png and /dev/null differ diff --git a/docs/plans/diagrams/native-alarms-task-dependency.drawio b/docs/plans/diagrams/native-alarms-task-dependency.drawio deleted file mode 100644 index 45780879..00000000 --- a/docs/plans/diagrams/native-alarms-task-dependency.drawio +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/native-alarms-task-dependency.png b/docs/plans/diagrams/native-alarms-task-dependency.png deleted file mode 100644 index 636dc69a..00000000 Binary files a/docs/plans/diagrams/native-alarms-task-dependency.png and /dev/null differ diff --git a/docs/plans/diagrams/opcua-config-model-architecture.drawio b/docs/plans/diagrams/opcua-config-model-architecture.drawio deleted file mode 100644 index 950d9533..00000000 --- a/docs/plans/diagrams/opcua-config-model-architecture.drawio +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/opcua-config-model-architecture.png b/docs/plans/diagrams/opcua-config-model-architecture.png deleted file mode 100644 index d9041018..00000000 Binary files a/docs/plans/diagrams/opcua-config-model-architecture.png and /dev/null differ diff --git a/docs/plans/diagrams/opcua-tag-browser-architecture.drawio b/docs/plans/diagrams/opcua-tag-browser-architecture.drawio deleted file mode 100644 index c65b5d98..00000000 --- a/docs/plans/diagrams/opcua-tag-browser-architecture.drawio +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/opcua-tag-browser-architecture.png b/docs/plans/diagrams/opcua-tag-browser-architecture.png deleted file mode 100644 index 98328236..00000000 Binary files a/docs/plans/diagrams/opcua-tag-browser-architecture.png and /dev/null differ diff --git a/docs/plans/diagrams/opcua-tag-browser-command-flow.drawio b/docs/plans/diagrams/opcua-tag-browser-command-flow.drawio deleted file mode 100644 index b8a4b9a7..00000000 --- a/docs/plans/diagrams/opcua-tag-browser-command-flow.drawio +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/opcua-tag-browser-command-flow.png b/docs/plans/diagrams/opcua-tag-browser-command-flow.png deleted file mode 100644 index 257e51c3..00000000 Binary files a/docs/plans/diagrams/opcua-tag-browser-command-flow.png and /dev/null differ diff --git a/docs/plans/diagrams/primary-backup-failover-state-machine.drawio b/docs/plans/diagrams/primary-backup-failover-state-machine.drawio deleted file mode 100644 index d22bcacc..00000000 --- a/docs/plans/diagrams/primary-backup-failover-state-machine.drawio +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/plans/diagrams/primary-backup-failover-state-machine.png b/docs/plans/diagrams/primary-backup-failover-state-machine.png deleted file mode 100644 index 32088bfb..00000000 Binary files a/docs/plans/diagrams/primary-backup-failover-state-machine.png and /dev/null differ diff --git a/docs/plans/generate_plans.md b/docs/plans/generate_plans.md index 582defeb..2a3bb2c5 100644 --- a/docs/plans/generate_plans.md +++ b/docs/plans/generate_plans.md @@ -547,8 +547,27 @@ This section governs how implementation plans are executed. The goal is autonomo For each work package, follow this sequence: -![generate-plans-per-work-package](diagrams/generate-plans-per-work-package.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + S1["1. READ the WP description and acceptance criteria"] + S2["2. READ all traced requirements (HLR bullets, KDD, CD constraints) to understand intent"] + S3["3. IMPLEMENT the WP
• Write code
• Write unit tests for acceptance criteria
• Write negative tests for prohibition criteria"] + S4["4. VERIFY acceptance criteria
• Run tests: all must pass
• Walk each acceptance criterion line by line
• If a criterion cannot be verified yet (depends on a later WP), note it as deferred to WP-N"] + S5["5. UPDATE the phase execution checklist
• Mark WP as complete with date
• Note any deferred criteria
• Note any questions logged"] + S6["6. COMMIT with message:
Phase N WP-M: summary"] + + S1 --> S2 --> S3 --> S4 --> S5 --> S6 + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + class S1,S2 proc + class S3 start + class S4,S5 dec + class S6 warn +``` ### Mid-Phase Compliance Check diff --git a/docs/plans/grpc_streams.md b/docs/plans/grpc_streams.md index 31a60a07..130816eb 100644 --- a/docs/plans/grpc_streams.md +++ b/docs/plans/grpc_streams.md @@ -23,8 +23,39 @@ gRPC server-streaming is an established pattern for real-time tag value updates; ## Architecture -![grpc-streams-architecture](diagrams/grpc-streams-architecture.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + subgraph CENTRAL["Central Cluster"] + BT["DebugStreamBridgeActor"] + GC["SiteStreamGrpcClient
(per-site, on central)"] + BB["DebugStreamBridgeActor"] + SR(["SignalR Hub / Blazor UI"]) + end + + subgraph SITE["Site Cluster"] + IN["InstanceActor"] + PB{"publishes
AttributeValueChanged
AlarmStateChanged"} + GS["SiteStreamGrpcServer
(Kestrel, on site)"] + end + + BT -.->|"SubscribeDebugView"| IN + IN -.->|"DebugViewSnapshot"| BT + IN --> PB + PB --> GS + GS -->|"gRPC stream (HTTP/2)"| GC + GC --> BB + BB --> SR + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + class BT,GC,BB proc + class SR start + class IN,GS warn + class PB dec +``` **Key separation**: ClusterClient handles subscribe/unsubscribe/snapshot (request-response). gRPC handles the ongoing value stream (server-streaming). @@ -250,8 +281,23 @@ public override async Task SubscribeInstance( `IServerStreamWriter` is **not thread-safe**. Multiple Akka actors may publish events concurrently. The `Channel` bridges these worlds: -![grpc-channel-bridging](diagrams/grpc-channel-bridging.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + AKKA["Akka Actor Thread(s)"] + CH(["Channel<SiteStreamEvent>

BoundedChannelOptions(1000)
FullMode = DropOldest"]) + GRPC["gRPC Response Stream"] + + AKKA -->|"channel.Writer.TryWrite(evt)"| CH + CH -->|"await responseStream.WriteAsync(evt)"| GRPC + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + class AKKA warn + class CH start + class GRPC proc +``` - **Bounded capacity** (1000): prevents unbounded memory growth if the gRPC client is slow - **DropOldest**: matches the existing `SiteStreamManager` overflow strategy @@ -401,8 +447,33 @@ private void HandleGrpcStreamError(Exception ex) ### Reconnection State Machine (DebugStreamBridgeActor) -![grpc-reconnection-state-machine](diagrams/grpc-reconnection-state-machine.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + S(["Streaming
Normal state: gRPC stream active"]) + R(["Reconnecting
try other node endpoint"]) + D{"reconnect result?"} + RT["schedule retry
(5s backoff)"] + T(["Terminated
notify consumer, stop actor"]) + + S -->|"gRPC stream error / keepalive timeout"| R + R --> D + D -->|"success"| S + D -->|"failure (retry < max)"| RT + RT --> R + D -->|"failure (retry >= max)"| T + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef bad fill:#f8cecc,stroke:#b85450,color:#111111; + class S start + class R dec + class D proc + class RT warn + class T bad +``` ### Summary diff --git a/docs/requirements/Component-Communication.md b/docs/requirements/Component-Communication.md index 452bcd3f..69d85d95 100644 --- a/docs/requirements/Component-Communication.md +++ b/docs/requirements/Component-Communication.md @@ -167,8 +167,55 @@ Keepalive settings are configurable via `CommunicationOptions`: ## Topology -![communication-topology](diagrams/communication-topology.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart LR + subgraph Central["Central Cluster"] + CCA["ClusterClient
(command/control)"] + CCB["ClusterClient
(command/control)"] + CCN["ClusterClient
(command/control)"] + GRPCC["SiteStreamGrpcClient
(real-time data)"] + end + + subgraph SiteA["Site A Cluster"] + SACOMM["SiteCommunicationActor
(via Receptionist)"] + SAGRPC["SiteStreamGrpcServer
(Kestrel HTTP/2, port 8083)"] + SACC["ClusterClient to Central
(CentralCommunicationActor)"] + end + + subgraph SiteB["Site B Cluster"] + SBCOMM["SiteCommunicationActor
(via Receptionist)"] + SBGRPC["SiteStreamGrpcServer"] + end + + subgraph SiteN["Site N Cluster"] + SNCOMM["SiteCommunicationActor
(via Receptionist)"] + SNGRPC["SiteStreamGrpcServer"] + end + + CCA -->|command/control| SACOMM + CCB -->|command/control| SBCOMM + CCN -->|command/control| SNCOMM + + SAGRPC -->|"gRPC stream (real-time data)"| GRPCC + SBGRPC -->|gRPC stream| GRPCC + SNGRPC -->|gRPC stream| GRPCC + + SACC -.->|command/control| Central + + NOTE["Sites do NOT communicate with each other.
All inter-cluster communication flows through Central."] + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class CCA,CCB,CCN,SACOMM,SACC,SBCOMM,SNCOMM dec + class GRPCC,SAGRPC,SBGRPC,SNGRPC start + class NOTE muted + class Central proc + class SiteA,SiteB,SiteN alt +``` - Sites do **not** communicate with each other. - All inter-cluster communication flows through central. diff --git a/docs/requirements/Component-ConfigurationDatabase.md b/docs/requirements/Component-ConfigurationDatabase.md index 61fe6e61..25f345e0 100644 --- a/docs/requirements/Component-ConfigurationDatabase.md +++ b/docs/requirements/Component-ConfigurationDatabase.md @@ -143,8 +143,33 @@ EF Core's DbContext naturally provides unit-of-work semantics: ### Example Transactional Flow -![configdb-transactional-flow](diagrams/configdb-transactional-flow.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + start(["Template Engine: Create Template"]) + add1["repository.AddTemplate(template)
// template is a Commons POCO"] + add2["repository.AddAttributes(attributes)
// attributes are Commons POCOs"] + add3["repository.AddAlarms(alarms)
// alarms are Commons POCOs"] + add4["repository.AddScripts(scripts)
// scripts are Commons POCOs"] + save["repository.SaveChangesAsync()
// single transaction commits all"] + db[("Configuration DB
(MS SQL)")] + + start --> add1 + add1 --> add2 + add2 --> add3 + add3 --> add4 + add4 --> save + save -. "single transaction" .-> db + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class start start + class add1,add2,add3,add4 proc + class save dec + class db muted +``` --- @@ -177,8 +202,31 @@ Audit entries are written **synchronously** within the same database transaction ### Integration Example -![configdb-integration-example](diagrams/configdb-integration-example.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + start(["Template Engine: Update Template"]) + upd["repository.UpdateTemplate(template)"] + audit["auditService.LogAsync(user, "Update", "Template",
template.Id, template.Name, template)"] + save["repository.SaveChangesAsync()"] + note["both the change and audit entry
commit together"] + + start --> upd + upd --> audit + audit --> save + save -.- note + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + class start start + class upd proc + class audit alt + class save dec + class note warn +``` ### Audit Entry Schema diff --git a/docs/requirements/Component-DataConnectionLayer.md b/docs/requirements/Component-DataConnectionLayer.md index a39aecf2..d5db6b80 100644 --- a/docs/requirements/Component-DataConnectionLayer.md +++ b/docs/requirements/Component-DataConnectionLayer.md @@ -80,8 +80,39 @@ Data connections support an optional backup endpoint for automatic failover when **Failover state machine:** -![dcl-endpoint-redundancy](diagrams/dcl-endpoint-redundancy.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + connected(["Connected"]) + pushbad["push bad quality"] + retry["retry active endpoint
(5s)"] + decide{"N failures
(≥ FailoverRetryCount)?"} + switch["switch to other endpoint"] + dispose["dispose adapter,
create fresh adapter
with other config"] + reconnect["reconnect"] + resub["ReSubscribeAll"] + + connected -->|disconnect| pushbad + pushbad --> retry + retry --> decide + decide -->|"no (retry again)"| retry + decide -->|yes| switch + switch --> dispose + dispose --> reconnect + reconnect --> resub + resub -->|back to Connected| connected + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef bad fill:#f8cecc,stroke:#b85450,color:#111111; + class connected start + class pushbad bad + class retry,reconnect,resub proc + class decide dec + class switch,dispose warn +``` - **Round-robin**: primary → backup → primary → backup. No preferred endpoint after first failover — the connection stays on whichever endpoint is working. - **No auto-failback**: The connection remains on the active endpoint until it fails. diff --git a/docs/requirements/Component-DeploymentManager.md b/docs/requirements/Component-DeploymentManager.md index d194bdf9..637c1b21 100644 --- a/docs/requirements/Component-DeploymentManager.md +++ b/docs/requirements/Component-DeploymentManager.md @@ -22,8 +22,47 @@ Central cluster only. The site-side deployment responsibilities (receiving confi ## Deployment Flow -![deployment-flow](diagrams/deployment-flow.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + engineer(["Engineer (UI)"]) + + subgraph DMC["Deployment Manager (Central)"] + step1["1. Request validated and flattened config from Template Engine
(validation: flattening, script compilation, trigger references,
connection binding completeness)"] + step2{"2. Validation fails?"} + step2fail(["Return errors to UI, stop"]) + step3["3. Send config to site via Communication Layer"] + step8[("8. Update deployment status in config DB")] + end + + subgraph SR["Site Runtime (Deployment Manager Singleton)"] + step4[("4. Store new flattened config locally (SQLite)")] + step5["5. Compile scripts at site"] + step6["6. Create/update Instance Actor
(with child Script + Alarm Actors)"] + step7["7. Report success/failure back to central"] + end + + engineer --> step1 + step1 --> step2 + step2 -->|yes| step2fail + step2 -->|no| step3 + step3 -->|config| step4 + step4 --> step5 + step5 --> step6 + step6 --> step7 + step7 -. "report success/failure" .-> step8 + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef bad fill:#f8cecc,stroke:#b85450,color:#111111; + class engineer start + class step1,step5,step6,step7 dec + class step2,step2fail bad + class step3 dec + class step8 proc + class step4 start +``` ## Deployment Identity & Idempotency diff --git a/docs/requirements/Component-InboundAPI.md b/docs/requirements/Component-InboundAPI.md index 30c67aa7..b1817118 100644 --- a/docs/requirements/Component-InboundAPI.md +++ b/docs/requirements/Component-InboundAPI.md @@ -123,8 +123,41 @@ API method scripts are compiled at central startup — all method definitions ar ## Request Flow -![inboundapi-request-flow](diagrams/inboundapi-request-flow.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + ext(["External System"]) + api["Inbound API (Central)"] + s1["1. Extract API key from request"] + s2["2. Validate key exists and is enabled"] + s3["3. Resolve method by name"] + s4["4. Check API key is in method's approved list"] + s5["5. Validate and deserialize parameters"] + s6["6. Execute implementation script
(subject to method timeout)"] + s7["7. Serialize return value"] + s8["8. Return response"] + + ext --> api + api --> s1 + s1 --> s2 + s2 --> s3 + s3 --> s4 + s4 --> s5 + s5 --> s6 + s6 --> s7 + s7 --> s8 + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + class ext start + class api proc + class s1,s2,s3,s4,s5,s7 dec + class s6 alt + class s8 warn +``` ## Implementation Script Capabilities diff --git a/docs/requirements/Component-NotificationOutbox.md b/docs/requirements/Component-NotificationOutbox.md index bb5d170f..3b05dbd9 100644 --- a/docs/requirements/Component-NotificationOutbox.md +++ b/docs/requirements/Component-NotificationOutbox.md @@ -24,8 +24,40 @@ SMTP and HTTP delivery is blocking I/O. Delivery work runs on a **dedicated bloc ## End-to-End Flow -![notificationoutbox-flow](diagrams/notificationoutbox-flow.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + SCRIPT(["Site script: Notify.To('list').Send(subject, body)
generate NotificationId (GUID) locally;
return it to the script immediately"]) + SNF["Site Store-and-Forward Engine
(notification category, target = central)
durably forwards to central via Central-Site Communication
(ClusterClient); buffers/retries if central is unreachable"] + INGEST[("Central ingest: insert-if-not-exists on NotificationId
to Notifications table (Pending)
ack the site, site S and F clears the message")] + OUTBOX["Central Notification Outbox actor
(singleton, active central node)
polls due rows; resolves the list;
delivers via the matching adapter"] + D1{Delivery outcome} + DELIVERED(["Delivered"]) + RETRYING["Retrying
(schedule NextAttemptAt)"] + PARKED(["Parked"]) + + SCRIPT --> SNF + SNF --> INGEST + INGEST --> OUTBOX + OUTBOX --> D1 + D1 -->|success| DELIVERED + D1 -->|transient failure| RETRYING + D1 -->|"permanent failure /
retries exhausted"| PARKED + RETRYING -.->|retry due| OUTBOX + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef bad fill:#f8cecc,stroke:#b85450,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + class SCRIPT,DELIVERED start + class SNF warn + class INGEST proc + class OUTBOX alt + class D1,RETRYING dec + class PARKED bad +``` The site forwards only `(listName, subject, body)` plus provenance — recipient resolution happens at central, at delivery time. This keeps notification-list definitions in one place and removes the deploy-to-sites artifact entirely. diff --git a/docs/requirements/Component-SiteRuntime.md b/docs/requirements/Component-SiteRuntime.md index a4f717e5..05bdb45b 100644 --- a/docs/requirements/Component-SiteRuntime.md +++ b/docs/requirements/Component-SiteRuntime.md @@ -27,8 +27,57 @@ Site clusters only. ## Actor Hierarchy -![siteruntime-actor-hierarchy](diagrams/siteruntime-actor-hierarchy.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + DMS["Deployment Manager Singleton
(Cluster Singleton)"] + IA1["Instance Actor
('MachineA-001')"] + IA2["Instance Actor
('MachineA-002')"] + IAMORE["… more Instance Actors"] + + SA1["Script Actor ('MonitorSpeed')
— coordinator"] + SA2["Script Actor ('CalculateOEE')
— coordinator"] + AA1["Alarm Actor ('OverTemp')
— coordinator (computed)"] + AA2["Alarm Actor ('LowPressure')
— coordinator (computed)"] + NAA1["Native Alarm Actor ('OpcUaServer1')
— read-only mirror, peer to Alarm Actor"] + + SEA1["Script Execution Actor
— short-lived, per invocation"] + SEA2["Script Execution Actor
— short-lived, per invocation"] + AEA1["Alarm Execution Actor
— short-lived, per on-trigger invocation"] + + IA2CHILD["… (Script / Alarm Actors)"] + + DMS --> IA1 + DMS --> IA2 + DMS -.-> IAMORE + + IA1 --> SA1 + IA1 --> SA2 + IA1 --> AA1 + IA1 --> AA2 + IA1 --> NAA1 + + SA1 --> SEA1 + SA2 --> SEA2 + AA1 --> AEA1 + + IA2 -.-> IA2CHILD + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef bad fill:#f8cecc,stroke:#b85450,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class DMS proc + class IA1,IA2 start + class SA1,SA2 dec + class AA1,AA2 bad + class NAA1 alt + class SEA1,SEA2,AEA1 warn + class IAMORE,IA2CHILD muted +``` --- diff --git a/docs/requirements/Component-Transport.md b/docs/requirements/Component-Transport.md index fd4dc4de..7e872aa6 100644 --- a/docs/requirements/Component-Transport.md +++ b/docs/requirements/Component-Transport.md @@ -92,8 +92,32 @@ The manifest is plaintext so the import wizard can preview bundle contents and s ## Architecture -![transport-architecture](diagrams/transport-architecture.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + subgraph T["ZB.MOM.WW.ScadaBridge.Transport"] + EXPORTER["IBundleExporter
ExportAsync(ExportSelection, Passphrase?, ct) → Stream"] + IMPORTER["IBundleImporter
LoadAsync(stream, Passphrase?, ct) → BundleSession
PreviewAsync(sessionId, ct) → ImportPreview
ApplyAsync(sessionId, resolutions, ct) → ImportResult"] + RESOLVER["DependencyResolver"] + SERIALIZER["BundleSerializer
(manifest + content JSON; ZIP packer)"] + ENCRYPTOR["BundleSecretEncryptor
(AES-256-GCM + PBKDF2)"] + SESSIONSTORE["BundleSessionStore
(in-memory, TTL'd)"] + MANIFESTVALIDATOR["ManifestValidator
(schema/version gating, hash check)"] + end + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class EXPORTER,IMPORTER proc + class RESOLVER,SERIALIZER start + class ENCRYPTOR alt + class SESSIONSTORE warn + class MANIFESTVALIDATOR dec + class T muted +``` The component is central-only. It is registered in `ZB.MOM.WW.ScadaBridge.Host` for central roles only, never for site roles. All persistence flows through existing audited repository interfaces in `ZB.MOM.WW.ScadaBridge.ConfigurationDatabase` — the component does not call `DbContext.SaveChangesAsync` directly. `BundleSessionStore` is in-process on the active central node (matching Blazor Server circuit affinity): 30-minute TTL, eviction on expiry, 3-strike passphrase lockout per session. @@ -120,8 +144,50 @@ The user can toggle "include all dependencies" off (with a warning that the bund ### Backend -![transport-export-flow](diagrams/transport-export-flow.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + USER(["User (Design role)"]) + WIZARD["Central UI Export wizard"] + EXPORTER["IBundleExporter"] + RESOLVER["DependencyResolver"] + REPOS[("repositories (read)")] + SERIALIZER["EntitySerializer"] + CONTENTJSON["content.json"] + ENCRYPTOR["BundleSecretEncryptor"] + CONTENTENC["content.enc
(if passphrase)"] + MANIFESTBUILDER["ManifestBuilder"] + MANIFESTJSON["manifest.json"] + ZIP["ZIP packer → temp file → browser download"] + AUDIT["IAuditService.LogAsync(BundleExported …)"] + + USER --> WIZARD + WIZARD --> EXPORTER + EXPORTER --> RESOLVER + RESOLVER --> SERIALIZER + SERIALIZER --> ENCRYPTOR + ENCRYPTOR --> MANIFESTBUILDER + MANIFESTBUILDER --> ZIP + ZIP --> AUDIT + + RESOLVER --> REPOS + SERIALIZER --> CONTENTJSON + ENCRYPTOR --> CONTENTENC + MANIFESTBUILDER --> MANIFESTJSON + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class USER,AUDIT start + class WIZARD,EXPORTER,ZIP proc + class RESOLVER,SERIALIZER,MANIFESTBUILDER dec + class ENCRYPTOR alt + class CONTENTJSON,CONTENTENC,MANIFESTJSON warn + class REPOS muted +``` Audit event: `BundleExported` — caller, artifact count, content hash, encrypted yes/no, bundle filename. @@ -153,8 +219,37 @@ Bundle references that cannot be satisfied in either the bundle or the target DB ### Backend -![transport-import-flow](diagrams/transport-import-flow.png) - +```mermaid +%%{init: {'theme':'base', 'themeVariables': {'textColor':'#111111','lineColor':'#555555','edgeLabelBackground':'#ffffff','fontSize':'15px'}}}%% +flowchart TD + USER(["User (Admin role) → uploads bundle"]) + LOAD["IBundleImporter.LoadAsync
· verify SHA-256 (manifest vs content)
· check bundleFormatVersion supported
· decrypt content.enc with passphrase (if encrypted)
· deserialize entities
· open BundleSession (30-min TTL)"] + PREVIEW["PreviewAsync → diff vs target DB → ImportPreview"] + REVIEW["(user reviews + resolves conflicts)"] + APPLY["ApplyAsync (single EF transaction)
· run two-tier semantic validation
  (minimal name scan + full SemanticValidator)
· apply resolutions (add / overwrite / skip / rename)
· upsert TemplateFolder hierarchy
· IAuditService.LogAsync(BundleImported …)
· commit"] + RESULT["ImportResult → UI step 5"] + DEPLOYMENTS["'View on Deployments →' (existing page)"] + + USER --> LOAD + LOAD --> PREVIEW + PREVIEW --> APPLY + PREVIEW -.- REVIEW + APPLY --> RESULT + RESULT --> DEPLOYMENTS + + classDef start fill:#d5e8d4,stroke:#82b366,color:#111111; + classDef proc fill:#dae8fc,stroke:#6c8ebf,color:#111111; + classDef dec fill:#fff2cc,stroke:#d6b656,color:#111111; + classDef warn fill:#ffe6cc,stroke:#d79b00,color:#111111; + classDef alt fill:#e1d5e7,stroke:#9673a6,color:#111111; + classDef muted fill:#f5f5f5,stroke:#999999,color:#666666; + class USER start + class LOAD,RESULT proc + class PREVIEW dec + class APPLY alt + class DEPLOYMENTS warn + class REVIEW muted +``` Authorization: `RequireAdmin` on both the Razor page and `IBundleImporter.*` entrypoints. diff --git a/docs/requirements/diagrams/communication-topology.drawio b/docs/requirements/diagrams/communication-topology.drawio deleted file mode 100644 index e04988a8..00000000 --- a/docs/requirements/diagrams/communication-topology.drawio +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/communication-topology.png b/docs/requirements/diagrams/communication-topology.png deleted file mode 100644 index 1db8a2f2..00000000 Binary files a/docs/requirements/diagrams/communication-topology.png and /dev/null differ diff --git a/docs/requirements/diagrams/configdb-integration-example.drawio b/docs/requirements/diagrams/configdb-integration-example.drawio deleted file mode 100644 index 109c1d41..00000000 --- a/docs/requirements/diagrams/configdb-integration-example.drawio +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/configdb-integration-example.png b/docs/requirements/diagrams/configdb-integration-example.png deleted file mode 100644 index 08896e75..00000000 Binary files a/docs/requirements/diagrams/configdb-integration-example.png and /dev/null differ diff --git a/docs/requirements/diagrams/configdb-transactional-flow.drawio b/docs/requirements/diagrams/configdb-transactional-flow.drawio deleted file mode 100644 index ca3e4c36..00000000 --- a/docs/requirements/diagrams/configdb-transactional-flow.drawio +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/configdb-transactional-flow.png b/docs/requirements/diagrams/configdb-transactional-flow.png deleted file mode 100644 index 473f7c58..00000000 Binary files a/docs/requirements/diagrams/configdb-transactional-flow.png and /dev/null differ diff --git a/docs/requirements/diagrams/dcl-endpoint-redundancy.drawio b/docs/requirements/diagrams/dcl-endpoint-redundancy.drawio deleted file mode 100644 index 6ecdc9c2..00000000 --- a/docs/requirements/diagrams/dcl-endpoint-redundancy.drawio +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/dcl-endpoint-redundancy.png b/docs/requirements/diagrams/dcl-endpoint-redundancy.png deleted file mode 100644 index d9a6a574..00000000 Binary files a/docs/requirements/diagrams/dcl-endpoint-redundancy.png and /dev/null differ diff --git a/docs/requirements/diagrams/deployment-flow.drawio b/docs/requirements/diagrams/deployment-flow.drawio deleted file mode 100644 index f9f72f66..00000000 --- a/docs/requirements/diagrams/deployment-flow.drawio +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/deployment-flow.png b/docs/requirements/diagrams/deployment-flow.png deleted file mode 100644 index 617f8b9d..00000000 Binary files a/docs/requirements/diagrams/deployment-flow.png and /dev/null differ diff --git a/docs/requirements/diagrams/inboundapi-request-flow.drawio b/docs/requirements/diagrams/inboundapi-request-flow.drawio deleted file mode 100644 index a6224027..00000000 --- a/docs/requirements/diagrams/inboundapi-request-flow.drawio +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/inboundapi-request-flow.png b/docs/requirements/diagrams/inboundapi-request-flow.png deleted file mode 100644 index 2d050a54..00000000 Binary files a/docs/requirements/diagrams/inboundapi-request-flow.png and /dev/null differ diff --git a/docs/requirements/diagrams/notificationoutbox-flow.drawio b/docs/requirements/diagrams/notificationoutbox-flow.drawio deleted file mode 100644 index 3c1ac72f..00000000 --- a/docs/requirements/diagrams/notificationoutbox-flow.drawio +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/notificationoutbox-flow.png b/docs/requirements/diagrams/notificationoutbox-flow.png deleted file mode 100644 index 7d81d2d6..00000000 Binary files a/docs/requirements/diagrams/notificationoutbox-flow.png and /dev/null differ diff --git a/docs/requirements/diagrams/siteruntime-actor-hierarchy.drawio b/docs/requirements/diagrams/siteruntime-actor-hierarchy.drawio deleted file mode 100644 index db10f222..00000000 --- a/docs/requirements/diagrams/siteruntime-actor-hierarchy.drawio +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/siteruntime-actor-hierarchy.png b/docs/requirements/diagrams/siteruntime-actor-hierarchy.png deleted file mode 100644 index 1584c20d..00000000 Binary files a/docs/requirements/diagrams/siteruntime-actor-hierarchy.png and /dev/null differ diff --git a/docs/requirements/diagrams/storeforward-message-lifecycle.drawio b/docs/requirements/diagrams/storeforward-message-lifecycle.drawio deleted file mode 100644 index a88c1ecb..00000000 --- a/docs/requirements/diagrams/storeforward-message-lifecycle.drawio +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/storeforward-message-lifecycle.png b/docs/requirements/diagrams/storeforward-message-lifecycle.png deleted file mode 100644 index 835d3471..00000000 Binary files a/docs/requirements/diagrams/storeforward-message-lifecycle.png and /dev/null differ diff --git a/docs/requirements/diagrams/transport-architecture.drawio b/docs/requirements/diagrams/transport-architecture.drawio deleted file mode 100644 index c767292a..00000000 --- a/docs/requirements/diagrams/transport-architecture.drawio +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/transport-architecture.png b/docs/requirements/diagrams/transport-architecture.png deleted file mode 100644 index dc7a9344..00000000 Binary files a/docs/requirements/diagrams/transport-architecture.png and /dev/null differ diff --git a/docs/requirements/diagrams/transport-export-flow.drawio b/docs/requirements/diagrams/transport-export-flow.drawio deleted file mode 100644 index 558ac422..00000000 --- a/docs/requirements/diagrams/transport-export-flow.drawio +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/transport-export-flow.png b/docs/requirements/diagrams/transport-export-flow.png deleted file mode 100644 index cffaa557..00000000 Binary files a/docs/requirements/diagrams/transport-export-flow.png and /dev/null differ diff --git a/docs/requirements/diagrams/transport-import-flow.drawio b/docs/requirements/diagrams/transport-import-flow.drawio deleted file mode 100644 index c0831bbb..00000000 --- a/docs/requirements/diagrams/transport-import-flow.drawio +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/requirements/diagrams/transport-import-flow.png b/docs/requirements/diagrams/transport-import-flow.png deleted file mode 100644 index b3646b96..00000000 Binary files a/docs/requirements/diagrams/transport-import-flow.png and /dev/null differ