docs: render architecture & flow diagrams as draw.io charts

Replace ASCII-art diagrams across the README and docs/ with editable
.drawio sources plus exported PNGs, so the diagrams render clearly in
rendered markdown and can be maintained/regenerated instead of being
hand-edited as fragile text art. Non-diagram blocks (code, folder
trees, UI wireframes) were left as text.
This commit is contained in:
Joseph Doherty
2026-05-31 23:32:53 -04:00
parent 3763f6d2d8
commit bdee12f4e9
71 changed files with 2461 additions and 516 deletions
@@ -0,0 +1,90 @@
<mxfile host="app.diagrams.net">
<diagram id="topology" name="Topology">
<mxGraphModel dx="1200" dy="800" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="800" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Users -->
<mxCell id="users" value="Users&#10;(HTTPS / LB)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="40" y="100" width="140" height="60" as="geometry" />
</mxCell>
<!-- Central Cluster container -->
<mxCell id="central" value="Central Cluster" style="swimlane;whiteSpace=wrap;html=1;startSize=30;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;fontStyle=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="320" y="60" width="380" height="150" as="geometry" />
</mxCell>
<mxCell id="nodeA" value="Node A&#10;Active" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;fontStyle=1;" vertex="1" parent="central">
<mxGeometry x="40" y="60" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="nodeB" value="Node B&#10;Standby" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=13;fontStyle=1;" vertex="1" parent="central">
<mxGeometry x="220" y="60" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="ab" value="" style="edgeStyle=none;html=1;startArrow=block;endArrow=block;rounded=0;" edge="1" parent="central" source="nodeA" target="nodeB">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Users -> Central -->
<mxCell id="u2c" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="users" target="nodeA">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Sites -->
<mxCell id="site01" value="Site 01" style="swimlane;whiteSpace=wrap;html=1;startSize=26;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="60" y="420" width="220" height="120" as="geometry" />
</mxCell>
<mxCell id="s01a" value="A" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;fontStyle=1;" vertex="1" parent="site01">
<mxGeometry x="30" y="50" width="70" height="50" as="geometry" />
</mxCell>
<mxCell id="s01b" value="B" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=13;fontStyle=1;" vertex="1" parent="site01">
<mxGeometry x="120" y="50" width="70" height="50" as="geometry" />
</mxCell>
<mxCell id="site02" value="Site 02" style="swimlane;whiteSpace=wrap;html=1;startSize=26;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="320" y="420" width="220" height="120" as="geometry" />
</mxCell>
<mxCell id="s02a" value="A" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;fontStyle=1;" vertex="1" parent="site02">
<mxGeometry x="30" y="50" width="70" height="50" as="geometry" />
</mxCell>
<mxCell id="s02b" value="B" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=13;fontStyle=1;" vertex="1" parent="site02">
<mxGeometry x="120" y="50" width="70" height="50" as="geometry" />
</mxCell>
<mxCell id="site03" value="Site 03" style="swimlane;whiteSpace=wrap;html=1;startSize=26;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="580" y="420" width="220" height="120" as="geometry" />
</mxCell>
<mxCell id="s03a" value="A" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;fontStyle=1;" vertex="1" parent="site03">
<mxGeometry x="30" y="50" width="70" height="50" as="geometry" />
</mxCell>
<mxCell id="s03b" value="B" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=13;fontStyle=1;" vertex="1" parent="site03">
<mxGeometry x="120" y="50" width="70" height="50" as="geometry" />
</mxCell>
<mxCell id="siteN" value="Site N" style="swimlane;whiteSpace=wrap;html=1;startSize=26;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;verticalAlign=top;dashed=1;" vertex="1" parent="1">
<mxGeometry x="840" y="420" width="220" height="120" as="geometry" />
</mxCell>
<mxCell id="sNa" value="A" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;fontStyle=1;" vertex="1" parent="siteN">
<mxGeometry x="30" y="50" width="70" height="50" as="geometry" />
</mxCell>
<mxCell id="sNb" value="B" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=13;fontStyle=1;" vertex="1" parent="siteN">
<mxGeometry x="120" y="50" width="70" height="50" as="geometry" />
</mxCell>
<!-- Central -> Sites (fan-out from cluster bottom) -->
<mxCell id="c2s01" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="central" target="site01">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="c2s02" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="central" target="site02">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="c2s03" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="central" target="site03">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="c2sN" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="central" target="siteN">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

+2 -18
View File
@@ -6,24 +6,8 @@ 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.
```
┌──────────────────────────┐
│ Central Cluster │
│ ┌──────┐ ┌──────┐ │
Users ──────────► │ │Node A│◄──►│Node B│ │
(HTTPS/LB) │ │Active│ │Stby │ │
│ └──┬───┘ └──┬───┘ │
└─────┼───────────┼────────┘
│ │
┌───────────┼───────────┼───────────┐
│ │ │ │
┌─────▼─────┐ ┌──▼──────┐ ┌──▼──────┐ ┌──▼──────┐
│ Site 01 │ │ Site 02 │ │ Site 03 │ │ Site N │
│ ┌──┐ ┌──┐ │ │ ┌──┐┌──┐│ │ ┌──┐┌──┐│ │ ┌──┐┌──┐│
│ │A │ │B │ │ │ │A ││B ││ │ │A ││B ││ │ │A ││B ││
│ └──┘ └──┘ │ │ └──┘└──┘│ │ └──┘└──┘│ │ └──┘└──┘│
└───────────┘ └─────────┘ └─────────┘ └─────────┘
```
![topology-architecture-overview](diagrams/topology-architecture-overview.png)
<!-- source: diagrams/topology-architecture-overview.drawio — edit, then re-export with export-drawio.sh -->
## Central Cluster Setup
@@ -39,28 +39,8 @@ Both endpoints use the same `Protocol`. EF Core migration renames `Configuration
The `DataConnectionActor` Reconnecting state is extended:
```
Connected
│ disconnect detected
Push bad quality to all subscribers
Retry active endpoint (5s interval)
│ failure
_consecutiveFailures++
├─ < FailoverRetryCount → retry same endpoint
├─ ≥ FailoverRetryCount AND backup exists
│ → dispose adapter, switch _activeEndpoint, reset counter
│ → create fresh adapter with other config
│ → attempt connect
└─ ≥ FailoverRetryCount AND no backup
→ keep retrying indefinitely (current behavior)
```
![primary-backup-failover-state-machine](diagrams/primary-backup-failover-state-machine.png)
<!-- source: diagrams/primary-backup-failover-state-machine.drawio — edit, then re-export with export-drawio.sh -->
**On successful reconnect (either endpoint):**
1. Reset `_consecutiveFailures = 0`
@@ -32,34 +32,8 @@ We want a strongly-typed model for OPC UA endpoint configuration, a validator th
## Architecture
```
┌──────────────────────────────────────┐
│ ZB.MOM.WW.ScadaBridge.Commons │
│ Types/DataConnections/ │
│ OpcUaEndpointConfig.cs (POCO) │
│ OpcUaHeartbeatConfig.cs (POCO) │
│ OpcUaSecurityMode.cs (enum) │
│ Validators/ │
│ OpcUaEndpointConfigValidator.cs │
│ Serialization/ │
│ OpcUaEndpointConfigSerializer.cs │
└──────────────────────────────────────┘
│ (referenced by both)
┌───────┴────────────────────────┐
▼ ▼
┌──────────────────────────┐ ┌────────────────────────────┐
│ ZB.MOM.WW.ScadaBridge.CentralUI │ │ ZB.MOM.WW.ScadaBridge.SiteRuntime │
│ Components/Forms/ │ │ Actors/ │
│ OpcUaEndpointEditor │ │ DeploymentManagerActor │
│ .razor (shared) │ │ (passes raw JSON to │
│ │ │ DataConnectionFactory)│
│ Pages/Admin/ │ │ │
│ DataConnectionForm │ │ DataConnections.OpcUa/ │
│ .razor │ │ OpcUaDataConnection.cs │
└──────────────────────────┘ │ (consumes typed model) │
└────────────────────────────┘
```
![opcua-config-model-architecture](diagrams/opcua-config-model-architecture.png)
<!-- source: diagrams/opcua-config-model-architecture.drawio — edit, then re-export with export-drawio.sh -->
Both sides deserialize from `DataConnection.PrimaryConfiguration` / `BackupConfiguration` strings into the same `OpcUaEndpointConfig` instance. The DB column type does not change.
@@ -17,29 +17,8 @@ A sibling `docker-env2/` directory with `deploy.sh` / `teardown.sh` / `seed-site
## Architecture Overview
```
(host machine)
Primary stack (already existing — unchanged) Env2 stack (new)
┌────────────────────────────────────┐ ┌──────────────────────────────┐
│ Traefik :9000 ◄── 9001/9002 UI │ │ Traefik :9100 ◄── 9101/9102 UI│
│ Central A/B (9011/9012 Akka) │ │ Central A/B (9111/9112 Akka) │
│ Site-A/B/C (9021..9044) │ │ Site-X (9121/9122 Akka, │
└─────────────┬──────────────────────┘ │ 9123/9124 gRPC) │
│ └──────────┬───────────────────┘
│ │
▼ scadabridge-net (shared bridge network) ◄──────┘
┌──────────────────────────────────────────────────────────────┐
│ scadabridge-mssql ScadaBridgeConfig (primary DB) │
│ ScadaBridgeMachineData (primary DB) │
│ ScadaBridgeConfig2 (env2 DB) ← new │
│ ScadaBridgeMachineData2(env2 DB) ← new │
│ scadabridge-ldap (shared — same test users) │
│ scadabridge-smtp (shared Mailpit) │
│ scadabridge-opcua (shared) │
│ scadabridge-restapi (shared) │
└──────────────────────────────────────────────────────────────┘
```
![env2-architecture-overview](diagrams/env2-architecture-overview.png)
<!-- source: diagrams/env2-architecture-overview.drawio — edit, then re-export with export-drawio.sh -->
Both stacks attach to the same `scadabridge-net` Docker bridge so env2's app containers can reach the infra services by container hostname (`scadabridge-mssql`, `scadabridge-ldap`, etc.). Akka clusters are independent — each side's `SeedNodes` lists only its own central nodes, so they never gossip-merge despite sharing the network.
+2 -13
View File
@@ -14,19 +14,8 @@
## Task Dependency Graph
```
T0 ─┐ ┐
T1 ─┤ (all independent, all │
T2 ─┤ parallelizable, all ├─► T10 (manual smoke test)
T3 ─┤ ready from the start) │
T4 ─┤ │
T6 ─┤ │
T7 ─┤ │
T8 ─┤ │
T9 ─┘ │
T0,T4 ──► T5 (lifecycle scripts) ─────────┘
```
![env2-task-dependency-graph](diagrams/env2-task-dependency-graph.png)
<!-- source: diagrams/env2-task-dependency-graph.drawio — edit, then re-export with export-drawio.sh -->
T10 is the only task that requires all of T0T9 done. Everything else can run in parallel.
@@ -18,27 +18,8 @@
## Section 1 — Architecture
```
[Blazor Server browser]
│ SignalR
[CentralUI: InstanceConfigure.razor]
│ opens
[CentralUI: <OpcUaBrowserDialog/>]
│ uses
[CentralUI: IOpcUaBrowseService] ── implementation calls
[CommunicationService.SendCommandToSiteAsync<BrowseOpcUaNodeResult>(siteId, BrowseOpcUaNodeCommand)]
│ ClusterClient Ask, ManagementEnvelope { User, Command, CorrelationId }
[Site: CentralCommunicationActor → DataConnectionManagerActor]
│ dispatches to IBrowsableDataConnection (RealOpcUaClient)
[OPC UA server] ◄── OPC Foundation .NET SDK Browse service
```
![opcua-tag-browser-architecture](diagrams/opcua-tag-browser-architecture.png)
<!-- source: diagrams/opcua-tag-browser-architecture.drawio — edit, then re-export with export-drawio.sh -->
Three slices, top-to-bottom:
1. **Data model.** Add a per-instance OPC UA address override (new column on `InstanceConnectionBinding`).
@@ -164,25 +145,8 @@ Returning failure inside `BrowseOpcUaNodeResult` (rather than exceptions across
**Wire flow.**
```
CentralUI.OpcUaBrowseService.BrowseChildrenAsync(siteId, connId, parent)
→ CommunicationService.SendCommandToSiteAsync<BrowseOpcUaNodeResult>(
siteId,
new BrowseOpcUaNodeCommand(connId, parent))
→ ManagementEnvelope { User, Command, CorrelationId } over ClusterClient
→ Site: CentralCommunicationActor unwraps envelope
→ Site: DataConnectionManagerActor receives BrowseOpcUaNodeCommand
- Look up IDataConnection by Id
- if not found → ConnectionNotFound
- if !(conn is IBrowsableDataConnection) → NotBrowsable
- else await conn.BrowseChildrenAsync(ParentNodeId, ct)
- Catch ConnectionNotConnectedException → ConnectionNotConnected
- Catch OperationCanceledException → Timeout
- Catch ServiceResultException → ServerError + verbatim msg
- Else success: BrowseOpcUaNodeResult(children, truncated, null)
→ Reply travels back via CentralCommunicationActor → CommunicationService
→ returned to CentralUI page
```
![opcua-tag-browser-command-flow](diagrams/opcua-tag-browser-command-flow.png)
<!-- source: diagrams/opcua-tag-browser-command-flow.drawio — edit, then re-export with export-drawio.sh -->
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.
+2 -17
View File
@@ -16,23 +16,8 @@
## Task dependency overview
```
T1 ─┬─ T2 ─┬─ T17 (computed AlarmActor enrich)
│ ├─ T18 (proto) ── T19 (grpc mapping) ── T23 (DebugView)
T3 ─┼─ T10 (DCL actor)
├─ T11 (OPC UA adapter)
└─ T12 (MxGateway adapter)
T4 ─┬─ T5 ── T6 ── T21 (mgmt handlers)
├─ T7 (migration)
├─ T8 ── T9 (validation)
└─ T20 ─┬─ T21 ── T26 (seed)
├─ T22 (CLI)
├─ T24 (template UI)
└─ T25 (instance UI)
T13, T14 ──┐
T1,T2,T3,T4(Resolved),T13,T14 ── T15 (NativeAlarmActor) ── T16 (InstanceActor wiring)
(everything) ── T27 (docs) , T28 (integration/manual verify)
```
![native-alarms-task-dependency](diagrams/native-alarms-task-dependency.png)
<!-- source: diagrams/native-alarms-task-dependency.drawio — edit, then re-export with export-drawio.sh -->
---
@@ -0,0 +1,96 @@
<mxfile host="app.diagrams.net">
<diagram id="env2arch" name="Env2 Architecture">
<mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="900" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Host machine container -->
<mxCell id="host" value="host machine" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#666666;verticalAlign=top;fontStyle=2;fontColor=#666666;dashed=1;" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="1020" height="800" as="geometry" />
</mxCell>
<!-- Primary stack -->
<mxCell id="primary" value="Primary stack&#10;(already existing — unchanged)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;verticalAlign=top;fontStyle=1;align=center;spacingTop=6;" vertex="1" parent="1">
<mxGeometry x="80" y="100" width="420" height="220" as="geometry" />
</mxCell>
<mxCell id="p-traefik" value="Traefik :9000 ◄── 9001/9002 UI" style="whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#6c8ebf;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="100" y="160" width="380" height="40" as="geometry" />
</mxCell>
<mxCell id="p-central" value="Central A/B (9011/9012 Akka)" style="whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#6c8ebf;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="100" y="210" width="380" height="40" as="geometry" />
</mxCell>
<mxCell id="p-site" value="Site-A/B/C (9021..9044)" style="whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#6c8ebf;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="100" y="260" width="380" height="40" as="geometry" />
</mxCell>
<!-- Env2 stack -->
<mxCell id="env2" value="Env2 stack&#10;(new)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;verticalAlign=top;fontStyle=1;align=center;spacingTop=6;" vertex="1" parent="1">
<mxGeometry x="600" y="100" width="420" height="220" as="geometry" />
</mxCell>
<mxCell id="e-traefik" value="Traefik :9100 ◄── 9101/9102 UI" style="whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#82b366;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="620" y="160" width="380" height="40" as="geometry" />
</mxCell>
<mxCell id="e-central" value="Central A/B (9111/9112 Akka)" style="whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#82b366;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="620" y="210" width="380" height="40" as="geometry" />
</mxCell>
<mxCell id="e-site" value="Site-X (9121/9122 Akka, 9123/9124 gRPC)" style="whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#82b366;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="620" y="260" width="380" height="40" as="geometry" />
</mxCell>
<!-- Shared network bar -->
<mxCell id="net" value="scadabridge-net (shared bridge network)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="400" width="940" height="50" as="geometry" />
</mxCell>
<!-- Infra container -->
<mxCell id="infra" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="120" y="500" width="860" height="300" as="geometry" />
</mxCell>
<!-- MSSQL block -->
<mxCell id="mssql" value="scadabridge-mssql" style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;verticalAlign=top;fontStyle=1;spacingTop=4;" vertex="1" parent="1">
<mxGeometry x="150" y="530" width="160" height="240" as="geometry" />
</mxCell>
<mxCell id="db1" value="ScadaBridgeConfig (primary DB)" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="340" y="540" width="300" height="40" as="geometry" />
</mxCell>
<mxCell id="db2" value="ScadaBridgeMachineData (primary DB)" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="340" y="590" width="300" height="40" as="geometry" />
</mxCell>
<mxCell id="db3" value="ScadaBridgeConfig2 (env2 DB) ← new" style="whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="340" y="640" width="300" height="40" as="geometry" />
</mxCell>
<mxCell id="db4" value="ScadaBridgeMachineData2 (env2 DB) ← new" style="whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="340" y="690" width="300" height="40" as="geometry" />
</mxCell>
<!-- Shared commodity infra services -->
<mxCell id="ldap" value="scadabridge-ldap&#10;(shared — same test users)" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="680" y="530" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="smtp" value="scadabridge-smtp&#10;(shared Mailpit)" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="680" y="580" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="opcua" value="scadabridge-opcua (shared)" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="680" y="630" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="restapi" value="scadabridge-restapi (shared)" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;spacingLeft=8;" vertex="1" parent="1">
<mxGeometry x="680" y="680" width="280" height="40" as="geometry" />
</mxCell>
<!-- Edges: primary -> net, env2 -> net -->
<mxCell id="ep" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="primary" target="net">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ee" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="env2" target="net">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- net -> infra -->
<mxCell id="eni" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#d6b656;" edge="1" parent="1" source="net" target="infra">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

@@ -0,0 +1,98 @@
<mxfile host="app.diagrams.net">
<diagram id="env2dag" name="Env2 Task Dependency Graph">
<mxGraphModel dx="1200" dy="900" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1000" pageHeight="800" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Independent / parallelizable group label -->
<mxCell id="grpLabel" value="all independent, all parallelizable, all ready from the start" style="text;html=1;align=center;verticalAlign=middle;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="40" y="30" width="300" height="30" as="geometry" />
</mxCell>
<!-- Independent task nodes (T0..T9 minus T5) -->
<mxCell id="t0" value="T0" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="80" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t1" value="T1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="130" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t2" value="T2" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="180" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t3" value="T3" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="230" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t4" value="T4" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="280" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t6" value="T6" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="330" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t7" value="T7" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="380" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t8" value="T8" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="430" width="80" height="40" as="geometry" />
</mxCell>
<mxCell id="t9" value="T9" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="480" width="80" height="40" as="geometry" />
</mxCell>
<!-- T5: lifecycle scripts (fed by T0 and T4) -->
<mxCell id="t5" value="T5&#10;lifecycle scripts" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="440" y="430" width="160" height="50" as="geometry" />
</mxCell>
<!-- T10: manual smoke test -->
<mxCell id="t10" value="T10&#10;manual smoke test" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="780" y="255" width="160" height="60" as="geometry" />
</mxCell>
<!-- Edges: each independent task -> T10 -->
<mxCell id="e0" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t0" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t1" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t2" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t3" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t6" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t7" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t8" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="t9" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- T0,T4 -> T5 -->
<mxCell id="e05" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#d79b00;" edge="1" parent="1" source="t0" target="t5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#d79b00;" edge="1" parent="1" source="t4" target="t5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- T5 -> T10 -->
<mxCell id="e510" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#d79b00;" edge="1" parent="1" source="t5" target="t10">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Note -->
<mxCell id="note" value="T10 is the only task that requires all of T0T9 done. Everything else runs in parallel." style="text;html=1;align=left;verticalAlign=middle;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="640" y="360" width="320" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

@@ -0,0 +1,41 @@
<mxfile host="app.diagrams.net">
<diagram id="perwp" name="Per-Work-Package Execution">
<mxGraphModel dx="900" dy="1100" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="700" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="s1" value="1. READ the WP description and acceptance criteria" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;verticalAlign=middle;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="180" y="40" width="380" height="50" as="geometry" />
</mxCell>
<mxCell id="s2" value="2. READ all traced requirements (HLR bullets, KDD, CD constraints) to understand intent" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;verticalAlign=middle;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="180" y="130" width="380" height="60" as="geometry" />
</mxCell>
<mxCell id="s3" value="3. IMPLEMENT the WP&#10;&#8226; Write code&#10;&#8226; Write unit tests for acceptance criteria&#10;&#8226; Write negative tests for prohibition criteria" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;spacingLeft=12;verticalAlign=middle;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="180" y="230" width="380" height="100" as="geometry" />
</mxCell>
<mxCell id="s4" value="4. VERIFY acceptance criteria&#10;&#8226; Run tests: all must pass&#10;&#8226; Walk each acceptance criterion line by line&#10;&#8226; If a criterion cannot be verified yet (depends on a later WP), note it as &quot;deferred to WP-N&quot;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;verticalAlign=middle;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="180" y="370" width="380" height="120" as="geometry" />
</mxCell>
<mxCell id="s5" value="5. UPDATE the phase execution checklist&#10;&#8226; Mark WP as complete with date&#10;&#8226; Note any deferred criteria&#10;&#8226; Note any questions logged" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;verticalAlign=middle;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="180" y="530" width="380" height="100" as="geometry" />
</mxCell>
<mxCell id="s6" value="6. COMMIT with message:&#10;&quot;Phase N WP-M: &lt;summary&gt;&quot;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;spacingLeft=12;verticalAlign=middle;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="180" y="670" width="380" height="60" as="geometry" />
</mxCell>
<!-- Edges -->
<mxCell id="e12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s1" target="s2"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s2" target="s3"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e34" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s3" target="s4"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s4" target="s5"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e56" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s5" target="s6"><mxGeometry relative="1" as="geometry" /></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

@@ -0,0 +1,41 @@
<mxfile host="app.diagrams.net">
<diagram id="channelbridge" name="Channel Bridging">
<mxGraphModel dx="1000" dy="700" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="640" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Producer side: Akka Actor Thread(s) -->
<mxCell id="akka" value="Akka Actor Thread(s)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="80" y="60" width="240" height="60" as="geometry" />
</mxCell>
<!-- Consumer side: gRPC Response Stream -->
<mxCell id="grpc" value="gRPC Response Stream" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="560" y="60" width="240" height="60" as="geometry" />
</mxCell>
<!-- Channel box -->
<mxCell id="channel" value="Channel&lt;SiteStreamEvent&gt;&#10;&#10;BoundedChannelOptions(1000)&#10;FullMode = DropOldest" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;fontStyle=1;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="240" y="280" width="400" height="120" as="geometry" />
</mxCell>
<!-- Producer writes into channel -->
<mxCell id="write" value="channel.Writer.TryWrite(evt)" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;fontSize=12;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="akka" target="channel">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Channel read out to gRPC stream -->
<mxCell id="read" value="await responseStream.WriteAsync(evt)" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;fontSize=12;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="channel" target="grpc">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Notes -->
<mxCell id="note" value="• Bounded capacity (1000): prevents unbounded memory growth if the gRPC client is slow&#10;• DropOldest: matches the existing SiteStreamManager overflow strategy&#10;• ReadAllAsync: yields items as they arrive, naturally async" style="text;html=1;align=left;verticalAlign=middle;fontSize=11;fontColor=#666666;spacingLeft=4;" vertex="1" parent="1">
<mxGeometry x="80" y="450" width="720" height="80" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

@@ -0,0 +1,83 @@
<mxfile host="app.diagrams.net">
<diagram id="statemachine" name="Reconnection State Machine">
<mxGraphModel dx="1000" dy="800" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="820" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Streaming state -->
<mxCell id="streaming" value="Streaming" style="rounded=1;arcSize=30;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="360" y="60" width="180" height="60" as="geometry" />
</mxCell>
<mxCell id="streamingNote" value="Normal state:&#10;gRPC stream active" style="text;html=1;align=left;verticalAlign=middle;fontSize=11;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="560" y="70" width="170" height="40" as="geometry" />
</mxCell>
<!-- Reconnecting state -->
<mxCell id="reconnecting" value="Reconnecting" style="rounded=1;arcSize=30;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="360" y="240" width="180" height="60" as="geometry" />
</mxCell>
<mxCell id="reconnectingNote" value="try other node endpoint" style="text;html=1;align=left;verticalAlign=middle;fontSize=11;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="560" y="250" width="170" height="40" as="geometry" />
</mxCell>
<!-- Decision diamond -->
<mxCell id="decide" value="reconnect&#10;result?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="380" y="400" width="140" height="90" as="geometry" />
</mxCell>
<!-- Schedule retry box -->
<mxCell id="retry" value="schedule retry&#10;(5s backoff)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="100" y="410" width="170" height="60" as="geometry" />
</mxCell>
<!-- Terminated state -->
<mxCell id="terminated" value="Terminated" style="rounded=1;arcSize=30;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="360" y="620" width="180" height="60" as="geometry" />
</mxCell>
<mxCell id="terminatedNote" value="notify consumer,&#10;stop actor" style="text;html=1;align=left;verticalAlign=middle;fontSize=11;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="560" y="630" width="170" height="40" as="geometry" />
</mxCell>
<!-- Transitions -->
<mxCell id="t_stream_recon" value="gRPC stream error /&#10;keepalive timeout" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;fontSize=11;" edge="1" parent="1" source="streaming" target="reconnecting">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="t_recon_decide" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;" edge="1" parent="1" source="reconnecting" target="decide">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- success -> Streaming -->
<mxCell id="t_success" value="success" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;fontSize=11;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="decide" target="streaming">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="700" y="445" />
<mxPoint x="700" y="90" />
</Array>
</mxGeometry>
</mxCell>
<!-- failure (retry < max) -> schedule retry -->
<mxCell id="t_retry" value="failure&#10;(retry &lt; max)" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;fontSize=11;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="decide" target="retry">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- schedule retry -> Reconnecting (loop back) -->
<mxCell id="t_loop" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="retry" target="reconnecting">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="185" y="270" />
</Array>
</mxGeometry>
</mxCell>
<!-- failure (retry >= max) -> Terminated -->
<mxCell id="t_term" value="failure&#10;(retry &gt;= max)" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;fontSize=11;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="decide" target="terminated">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

@@ -0,0 +1,102 @@
<mxfile host="app.diagrams.net">
<diagram id="grpcarch" name="gRPC Streams Architecture">
<mxGraphModel dx="1200" dy="900" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="1000" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Column headers -->
<mxCell id="hdrCentral" value="Central Cluster" style="text;html=1;align=center;verticalAlign=middle;fontSize=16;fontStyle=1;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="120" y="30" width="280" height="30" as="geometry" />
</mxCell>
<mxCell id="hdrSite" value="Site Cluster" style="text;html=1;align=center;verticalAlign=middle;fontSize=16;fontStyle=1;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="680" y="30" width="280" height="30" as="geometry" />
</mxCell>
<mxCell id="ruleC" value="" style="line;strokeWidth=2;html=1;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="120" y="60" width="280" height="10" as="geometry" />
</mxCell>
<mxCell id="ruleS" value="" style="line;strokeWidth=2;html=1;strokeColor=#d79b00;" vertex="1" parent="1">
<mxGeometry x="680" y="60" width="280" height="10" as="geometry" />
</mxCell>
<!-- CENTRAL column -->
<mxCell id="bridgeTop" value="DebugStreamBridgeActor" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="120" y="110" width="280" height="50" as="geometry" />
</mxCell>
<!-- SITE column -->
<mxCell id="instance" value="InstanceActor" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="680" y="110" width="280" height="50" as="geometry" />
</mxCell>
<!-- ClusterClient command/control between bridge and instance -->
<mxCell id="ccSub" value="SubscribeDebugView &#9658;" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;dashed=1;fontSize=11;exitX=1;exitY=0.3;exitDx=0;exitDy=0;entryX=0;entryY=0.3;entryDx=0;entryDy=0;verticalAlign=bottom;labelBackgroundColor=#ffffff;" edge="1" parent="1" source="bridgeTop" target="instance">
<mxGeometry relative="1" as="geometry">
<mxPoint x="0" y="-8" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="ccSnap" value="&#9668; DebugViewSnapshot" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;dashed=1;fontSize=11;exitX=0;exitY=0.7;exitDx=0;exitDy=0;entryX=1;entryY=0.7;entryDx=0;entryDy=0;verticalAlign=top;labelBackgroundColor=#ffffff;" edge="1" parent="1" source="instance" target="bridgeTop">
<mxGeometry relative="1" as="geometry">
<mxPoint x="0" y="8" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="ccLabel" value="ClusterClient:&#10;command / control" style="text;html=1;align=center;verticalAlign=middle;fontSize=10;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="455" y="178" width="170" height="34" as="geometry" />
</mxCell>
<!-- Instance publishes -->
<mxCell id="publishes" value="publishes&#10;AttributeValueChanged&#10;AlarmStateChanged" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="680" y="220" width="280" height="60" as="geometry" />
</mxCell>
<mxCell id="instPub" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;" edge="1" parent="1" source="instance" target="publishes">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- gRPC server (site) -->
<mxCell id="grpcServer" value="SiteStreamGrpcServer&#10;(Kestrel, on site)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="680" y="340" width="280" height="60" as="geometry" />
</mxCell>
<mxCell id="pub2srv" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;" edge="1" parent="1" source="publishes" target="grpcServer">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="srvNote1" value="receives from&#10;SiteStreamManager" style="text;html=1;align=left;verticalAlign=middle;fontSize=10;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="970" y="345" width="120" height="30" as="geometry" />
</mxCell>
<mxCell id="srvNote2" value="filters by&#10;instance name" style="text;html=1;align=left;verticalAlign=middle;fontSize=10;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="970" y="375" width="120" height="30" as="geometry" />
</mxCell>
<!-- gRPC client (central) -->
<mxCell id="grpcClient" value="SiteStreamGrpcClient&#10;(per-site, on central)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="120" y="340" width="280" height="60" as="geometry" />
</mxCell>
<!-- gRPC stream: server -> client -->
<mxCell id="grpcStream" value="gRPC stream (HTTP/2)" style="edgeStyle=none;html=1;endArrow=block;rounded=0;strokeWidth=2;strokeColor=#82b366;fontSize=12;fontStyle=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="grpcServer" target="grpcClient">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="cliNote" value="reads from gRPC stream&#10;routes by correlationId" style="text;html=1;align=center;verticalAlign=middle;fontSize=10;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="120" y="410" width="280" height="30" as="geometry" />
</mxCell>
<!-- bridge (central, lower) -->
<mxCell id="bridgeBottom" value="DebugStreamBridgeActor" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="120" y="460" width="280" height="50" as="geometry" />
</mxCell>
<mxCell id="cli2bridge" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;" edge="1" parent="1" source="grpcClient" target="bridgeBottom">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- SignalR / Blazor -->
<mxCell id="signalr" value="SignalR Hub / Blazor UI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="120" y="560" width="280" height="50" as="geometry" />
</mxCell>
<mxCell id="bridge2signalr" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;endArrow=block;rounded=0;" edge="1" parent="1" source="bridgeBottom" target="signalr">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

@@ -0,0 +1,158 @@
<mxfile host="app.diagrams.net">
<diagram id="nadag" name="Native Alarms Task Dependency">
<mxGraphModel dx="1600" dy="1100" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1500" pageHeight="1200" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- ============ Block 1: T1/T3 sources ============ -->
<mxCell id="t1" value="T1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="40" y="60" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="t3" value="T3" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="40" y="200" width="90" height="40" as="geometry" />
</mxCell>
<!-- T2 and the DCL/adapter fan-out from T1+T3 -->
<mxCell id="t2" value="T2" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="240" y="60" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="t10" value="T10&#10;DCL actor" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="240" y="180" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="t11" value="T11&#10;OPC UA adapter" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="240" y="240" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="t12" value="T12&#10;MxGateway adapter" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="240" y="300" width="140" height="40" as="geometry" />
</mxCell>
<!-- T2 fan-out: T17, T18->T19->T23 -->
<mxCell id="t17" value="T17&#10;computed AlarmActor enrich" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
<mxGeometry x="460" y="20" width="180" height="50" as="geometry" />
</mxCell>
<mxCell id="t18" value="T18&#10;proto" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
<mxGeometry x="460" y="100" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="t19" value="T19&#10;grpc mapping" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
<mxGeometry x="680" y="100" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="t23" value="T23&#10;DebugView" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
<mxGeometry x="900" y="100" width="140" height="40" as="geometry" />
</mxCell>
<!-- ============ Block 2: T4 source ============ -->
<mxCell id="t4" value="T4" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="40" y="500" width="90" height="40" as="geometry" />
</mxCell>
<!-- T4 fan-out -->
<mxCell id="t5" value="T5" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="240" y="420" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="t6" value="T6" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="400" y="420" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="t7" value="T7&#10;migration" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="240" y="480" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="t8" value="T8" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="240" y="540" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="t9" value="T9&#10;validation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="400" y="540" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="t20" value="T20" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="240" y="640" width="90" height="40" as="geometry" />
</mxCell>
<!-- T21: fed by both T6 and T20 -->
<mxCell id="t21" value="T21&#10;mgmt handlers" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="600" y="480" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="t26" value="T26&#10;seed" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="820" y="480" width="120" height="40" as="geometry" />
</mxCell>
<!-- T20 fan-out (others) -->
<mxCell id="t22" value="T22&#10;CLI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="460" y="620" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="t24" value="T24&#10;template UI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="460" y="680" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="t25" value="T25&#10;instance UI" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="460" y="740" width="120" height="40" as="geometry" />
</mxCell>
<!-- ============ Block 3: T13/T14 -> T15 -> T16 ============ -->
<mxCell id="t13" value="T13" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="40" y="860" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="t14" value="T14" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="40" y="920" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="t15" value="T15&#10;NativeAlarmActor" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="600" y="880" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="t16" value="T16&#10;InstanceActor wiring" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
<mxGeometry x="820" y="880" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="t15in" value="inputs to T15:&#10;T1, T2, T3, T4 (Resolved), T13, T14" style="text;html=1;align=left;verticalAlign=middle;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="180" y="880" width="380" height="50" as="geometry" />
</mxCell>
<!-- ============ Block 4: everything -> T27, T28 ============ -->
<mxCell id="t27" value="T27&#10;docs" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="1060" y="980" width="140" height="40" as="geometry" />
</mxCell>
<mxCell id="t28" value="T28&#10;integration / manual verify" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="1060" y="1040" width="200" height="40" as="geometry" />
</mxCell>
<mxCell id="evt" value="(everything) ──►" style="text;html=1;align=right;verticalAlign=middle;fontStyle=2;fontColor=#666666;" vertex="1" parent="1">
<mxGeometry x="880" y="990" width="170" height="40" as="geometry" />
</mxCell>
<!-- ============ Edges ============ -->
<!-- T1 + T3 -> T2, T10, T11, T12 -->
<mxCell id="b1t2a" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t1" target="t2"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="b1t10a" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t1" target="t10"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="b1t11a" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t1" target="t11"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="b1t12a" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t1" target="t12"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="b1t2b" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t3" target="t2"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="b1t10b" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t3" target="t10"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="b1t11b" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t3" target="t11"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="b1t12b" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#6c8ebf;" edge="1" parent="1" source="t3" target="t12"><mxGeometry relative="1" as="geometry" /></mxCell>
<!-- T2 -> T17, T18; T18 -> T19 -> T23 -->
<mxCell id="e_t2_17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#9673a6;" edge="1" parent="1" source="t2" target="t17"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_t2_18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#9673a6;" edge="1" parent="1" source="t2" target="t18"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_18_19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#9673a6;" edge="1" parent="1" source="t18" target="t19"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_19_23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#9673a6;" edge="1" parent="1" source="t19" target="t23"><mxGeometry relative="1" as="geometry" /></mxCell>
<!-- T4 -> T5, T7, T8, T20 -->
<mxCell id="e_4_5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t4" target="t5"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_4_7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t4" target="t7"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_4_8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t4" target="t8"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_4_20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t4" target="t20"><mxGeometry relative="1" as="geometry" /></mxCell>
<!-- T5 -> T6 -> T21; T8 -> T9 -->
<mxCell id="e_5_6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t5" target="t6"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_6_21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t6" target="t21"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_8_9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t8" target="t9"><mxGeometry relative="1" as="geometry" /></mxCell>
<!-- T20 -> T21, T22, T24, T25; T21 -> T26 -->
<mxCell id="e_20_21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t20" target="t21"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_20_22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t20" target="t22"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_20_24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t20" target="t24"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_20_25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t20" target="t25"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_21_26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#82b366;" edge="1" parent="1" source="t21" target="t26"><mxGeometry relative="1" as="geometry" /></mxCell>
<!-- T13, T14 -> T15 -> T16 -->
<mxCell id="e_13_15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#d79b00;" edge="1" parent="1" source="t13" target="t15"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_14_15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#d79b00;" edge="1" parent="1" source="t14" target="t15"><mxGeometry relative="1" as="geometry" /></mxCell>
<mxCell id="e_15_16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;strokeColor=#d79b00;" edge="1" parent="1" source="t15" target="t16"><mxGeometry relative="1" as="geometry" /></mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

@@ -0,0 +1,105 @@
<mxfile host="app.diagrams.net">
<diagram id="arch" name="Config Model Architecture">
<mxGraphModel dx="1200" dy="900" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="900" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Commons container -->
<mxCell id="commons" value="ZB.MOM.WW.ScadaBridge.Commons"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=14;fontStyle=1;verticalAlign=top;spacingTop=8;"
vertex="1" parent="1">
<mxGeometry x="360" y="40" width="380" height="270" as="geometry" />
</mxCell>
<!-- Types/DataConnections group -->
<mxCell id="types" value="Types/DataConnections/&#10;&#10;OpcUaEndpointConfig.cs (POCO)&#10;OpcUaHeartbeatConfig.cs (POCO)&#10;OpcUaSecurityMode.cs (enum)"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="380" y="80" width="340" height="100" as="geometry" />
</mxCell>
<!-- Validators group -->
<mxCell id="validators" value="Validators/&#10;OpcUaEndpointConfigValidator.cs"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="380" y="190" width="340" height="50" as="geometry" />
</mxCell>
<!-- Serialization group -->
<mxCell id="serialization" value="Serialization/&#10;OpcUaEndpointConfigSerializer.cs"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="380" y="250" width="340" height="50" as="geometry" />
</mxCell>
<!-- CentralUI container -->
<mxCell id="centralui" value="ZB.MOM.WW.ScadaBridge.CentralUI"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;fontStyle=1;verticalAlign=top;spacingTop=8;"
vertex="1" parent="1">
<mxGeometry x="120" y="480" width="320" height="220" as="geometry" />
</mxCell>
<mxCell id="cui_forms" value="Components/Forms/&#10;OpcUaEndpointEditor.razor (shared)"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="140" y="520" width="280" height="70" as="geometry" />
</mxCell>
<mxCell id="cui_pages" value="Pages/Admin/&#10;DataConnectionForm.razor"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="140" y="600" width="280" height="70" as="geometry" />
</mxCell>
<!-- SiteRuntime container -->
<mxCell id="siteruntime" value="ZB.MOM.WW.ScadaBridge.SiteRuntime"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;fontStyle=1;verticalAlign=top;spacingTop=8;"
vertex="1" parent="1">
<mxGeometry x="660" y="480" width="340" height="220" as="geometry" />
</mxCell>
<mxCell id="sr_actors" value="Actors/&#10;DeploymentManagerActor&#10;(passes raw JSON to DataConnectionFactory)"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="680" y="520" width="300" height="70" as="geometry" />
</mxCell>
<mxCell id="sr_dc" value="DataConnections.OpcUa/&#10;OpcUaDataConnection.cs&#10;(consumes typed model)"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="680" y="600" width="300" height="70" as="geometry" />
</mxCell>
<!-- referenced-by edges: both sides reference Commons -->
<mxCell id="ref_cui" value="referenced by"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;startArrow=none;fontSize=12;dashed=0;"
edge="1" parent="1" source="commons" target="centralui">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="280" y="400" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="ref_sr" value="referenced by"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;startArrow=none;fontSize=12;dashed=0;"
edge="1" parent="1" source="commons" target="siteruntime">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="830" y="400" />
</Array>
</mxGeometry>
</mxCell>
<!-- caption note -->
<mxCell id="note" value="Both sides deserialize DataConnection.PrimaryConfiguration / BackupConfiguration&#10;into the same OpcUaEndpointConfig instance. The DB column type does not change."
style="text;html=1;align=center;verticalAlign=middle;fontSize=12;fontStyle=2;fontColor=#555555;"
vertex="1" parent="1">
<mxGeometry x="200" y="730" width="720" height="50" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

@@ -0,0 +1,93 @@
<mxfile host="app.diagrams.net">
<diagram id="browserarch" name="Tag Browser Architecture">
<mxGraphModel dx="1000" dy="1200" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1300" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- 1. Blazor Server browser -->
<mxCell id="n1" value="Blazor Server browser"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="240" y="40" width="320" height="50" as="geometry" />
</mxCell>
<!-- 2. CentralUI: InstanceConfigure.razor -->
<mxCell id="n2" value="CentralUI: InstanceConfigure.razor"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="240" y="150" width="320" height="50" as="geometry" />
</mxCell>
<!-- 3. CentralUI: <OpcUaBrowserDialog/> -->
<mxCell id="n3" value="CentralUI: &amp;lt;OpcUaBrowserDialog/&amp;gt;"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="240" y="260" width="320" height="50" as="geometry" />
</mxCell>
<!-- 4. CentralUI: IOpcUaBrowseService -->
<mxCell id="n4" value="CentralUI: IOpcUaBrowseService"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="240" y="370" width="320" height="50" as="geometry" />
</mxCell>
<!-- 5. CommunicationService.SendCommandToSiteAsync -->
<mxCell id="n5" value="CommunicationService.SendCommandToSiteAsync&lt;BrowseOpcUaNodeResult&gt;(siteId, BrowseOpcUaNodeCommand)"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="180" y="480" width="440" height="60" as="geometry" />
</mxCell>
<!-- 6. Site: CentralCommunicationActor -> DataConnectionManagerActor -->
<mxCell id="n6" value="Site: CentralCommunicationActor &#8594; DataConnectionManagerActor"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="200" y="600" width="400" height="50" as="geometry" />
</mxCell>
<!-- 7. OPC UA server -->
<mxCell id="n7" value="OPC UA server"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=13;fontStyle=1;"
vertex="1" parent="1">
<mxGeometry x="280" y="710" width="240" height="50" as="geometry" />
</mxCell>
<!-- Edges with labels -->
<mxCell id="e1" value="SignalR"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;"
edge="1" parent="1" source="n1" target="n2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" value="opens"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;"
edge="1" parent="1" source="n2" target="n3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" value="uses"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;"
edge="1" parent="1" source="n3" target="n4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" value="implementation calls"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;"
edge="1" parent="1" source="n4" target="n5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" value="ClusterClient Ask&#10;ManagementEnvelope { User, Command, CorrelationId }"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="n5" target="n6">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" value="dispatches to IBrowsableDataConnection (RealOpcUaClient)&#10;OPC Foundation .NET SDK Browse service"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="n6" target="n7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

@@ -0,0 +1,155 @@
<mxfile host="app.diagrams.net">
<diagram id="cmdflow" name="Browse Command Flow">
<mxGraphModel dx="1200" dy="1400" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="1500" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Step 1: CentralUI.OpcUaBrowseService -->
<mxCell id="s1" value="CentralUI.OpcUaBrowseService.BrowseChildrenAsync(siteId, connId, parent)"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="120" y="40" width="440" height="50" as="geometry" />
</mxCell>
<!-- Step 2: CommunicationService.SendCommandToSiteAsync -->
<mxCell id="s2" value="CommunicationService.SendCommandToSiteAsync&lt;BrowseOpcUaNodeResult&gt;(&#10; siteId, new BrowseOpcUaNodeCommand(connId, parent))"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="120" y="150" width="440" height="60" as="geometry" />
</mxCell>
<!-- Step 3: ManagementEnvelope over ClusterClient -->
<mxCell id="s3" value="ManagementEnvelope { User, Command, CorrelationId }&#10;over ClusterClient"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="160" y="270" width="360" height="60" as="geometry" />
</mxCell>
<!-- Step 4: Site: CentralCommunicationActor unwraps envelope -->
<mxCell id="s4" value="Site: CentralCommunicationActor unwraps envelope"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="160" y="390" width="360" height="50" as="geometry" />
</mxCell>
<!-- Step 5: Site: DataConnectionManagerActor receives command -->
<mxCell id="s5" value="Site: DataConnectionManagerActor receives BrowseOpcUaNodeCommand&#10;(DCL coordinator actor — owns the per-connection IDataConnection instances)"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="120" y="490" width="440" height="60" as="geometry" />
</mxCell>
<!-- Handler logic box (the bulleted sub-steps) -->
<mxCell id="handler" value="Handler logic"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;fontStyle=1;verticalAlign=top;spacingTop=6;align=center;"
vertex="1" parent="1">
<mxGeometry x="660" y="470" width="380" height="380" as="geometry" />
</mxCell>
<mxCell id="h_lookup" value="Look up IDataConnection by Id"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#d6b656;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="500" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="h_notfound" value="if not found &#8594; ConnectionNotFound"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="540" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="h_notbrowsable" value="if !(conn is IBrowsableDataConnection) &#8594; NotBrowsable"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="580" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="h_await" value="else await conn.BrowseChildrenAsync(ParentNodeId, ct)"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#d6b656;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="620" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="h_notconn" value="Catch ConnectionNotConnectedException &#8594; ConnectionNotConnected"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="660" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="h_cancel" value="Catch OperationCanceledException &#8594; Timeout"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="700" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="h_svc" value="Catch ServiceResultException &#8594; ServerError + verbatim msg"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="740" width="340" height="30" as="geometry" />
</mxCell>
<mxCell id="h_success" value="Else success: BrowseOpcUaNodeResult(children, truncated, null)"
style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=11;align=left;spacingLeft=8;"
vertex="1" parent="1">
<mxGeometry x="680" y="790" width="340" height="40" as="geometry" />
</mxCell>
<!-- auth note -->
<mxCell id="authnote" value="Auth: site-side handler validates ManagementEnvelope.User has the Design role;&#10;otherwise reply ServerError (&quot;Not authorized to browse data connections&quot;)."
style="text;html=1;align=left;verticalAlign=middle;fontSize=11;fontStyle=2;fontColor=#555555;"
vertex="1" parent="1">
<mxGeometry x="120" y="570" width="430" height="40" as="geometry" />
</mxCell>
<!-- Reply path -->
<mxCell id="r1" value="Reply travels back via&#10;CentralCommunicationActor &#8594; CommunicationService"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="120" y="900" width="440" height="60" as="geometry" />
</mxCell>
<mxCell id="r2" value="returned to CentralUI page"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="200" y="1010" width="280" height="50" as="geometry" />
</mxCell>
<!-- Main chain edges -->
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;"
edge="1" parent="1" source="s1" target="s2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;"
edge="1" parent="1" source="s2" target="s3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;"
edge="1" parent="1" source="s3" target="s4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;"
edge="1" parent="1" source="s4" target="s5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- s5 -> handler logic -->
<mxCell id="e5" value="processes"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="s5" target="handler">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- handler -> reply -->
<mxCell id="e6" value="result / failure"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="handler" target="r1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="850" y="930" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;"
edge="1" parent="1" source="r1" target="r2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

@@ -0,0 +1,166 @@
<mxfile host="app.diagrams.net">
<diagram id="failover" name="Failover State Machine">
<mxGraphModel dx="1200" dy="900" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="1300" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Connected (start state) -->
<mxCell id="connected" value="Connected"
style="rounded=1;arcSize=40;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=14;fontStyle=1;"
vertex="1" parent="1">
<mxGeometry x="420" y="40" width="200" height="50" as="geometry" />
</mxCell>
<!-- Push bad quality -->
<mxCell id="badquality" value="Push bad quality&#10;to all subscribers"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="420" y="150" width="200" height="50" as="geometry" />
</mxCell>
<!-- Retry active endpoint -->
<mxCell id="retry" value="Retry active endpoint&#10;(5s interval)"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="420" y="260" width="200" height="50" as="geometry" />
</mxCell>
<!-- _consecutiveFailures++ -->
<mxCell id="incr" value="_consecutiveFailures++"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="420" y="370" width="200" height="50" as="geometry" />
</mxCell>
<!-- Branch decision diamond -->
<mxCell id="branch" value="Evaluate&#10;_consecutiveFailures"
style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;"
vertex="1" parent="1">
<mxGeometry x="450" y="470" width="140" height="100" as="geometry" />
</mxCell>
<!-- Branch 1: retry same endpoint -->
<mxCell id="same" value="Retry same endpoint"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="80" y="495" width="200" height="50" as="geometry" />
</mxCell>
<!-- Branch 2: failover to backup -->
<mxCell id="failover" value="Failover&#10;&#8226; dispose adapter, switch _activeEndpoint, reset counter&#10;&#8226; create fresh adapter with other config&#10;&#8226; attempt connect"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;align=left;spacingLeft=8;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="380" y="640" width="280" height="110" as="geometry" />
</mxCell>
<!-- Branch 3: no backup -->
<mxCell id="nobackup" value="Keep retrying indefinitely&#10;(current behavior)"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=13;"
vertex="1" parent="1">
<mxGeometry x="760" y="495" width="220" height="60" as="geometry" />
</mxCell>
<!-- Successful reconnect outcome -->
<mxCell id="reconnect" value="On successful reconnect (either endpoint)&#10;1. Reset _consecutiveFailures = 0&#10;2. ReSubscribeAll() — re-create subscriptions on new adapter&#10;3. Transition to Connected&#10;4. Log failover event if endpoint changed&#10;5. Report active endpoint in health metrics"
style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;align=left;spacingLeft=10;verticalAlign=middle;"
vertex="1" parent="1">
<mxGeometry x="360" y="830" width="320" height="140" as="geometry" />
</mxCell>
<!-- Edges -->
<mxCell id="e1" value="disconnect detected"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;"
edge="1" parent="1" source="connected" target="badquality">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;"
edge="1" parent="1" source="badquality" target="retry">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" value="failure"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;"
edge="1" parent="1" source="retry" target="incr">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;"
edge="1" parent="1" source="incr" target="branch">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Branch 1 edge -->
<mxCell id="b1" value="&lt; FailoverRetryCount"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="branch" target="same">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Branch 1 loops back to retry -->
<mxCell id="b1loop" value="retry"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;dashed=1;"
edge="1" parent="1" source="same" target="retry">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="180" y="285" />
</Array>
</mxGeometry>
</mxCell>
<!-- Branch 2 edge -->
<mxCell id="b2" value="&#8805; FailoverRetryCount&#10;AND backup exists"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="branch" target="failover">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Branch 3 edge -->
<mxCell id="b3" value="&#8805; FailoverRetryCount&#10;AND no backup"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="branch" target="nobackup">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- no backup loops back to retry -->
<mxCell id="b3loop" value="retry (round-robin n/a)"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;dashed=1;"
edge="1" parent="1" source="nobackup" target="retry">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="870" y="285" />
</Array>
</mxGeometry>
</mxCell>
<!-- failover -> attempt connect -> success -> reconnect -->
<mxCell id="b2ok" value="connect succeeds"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="failover" target="reconnect">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- failover -> attempt connect -> failure -> back to retry loop -->
<mxCell id="b2fail" value="connect fails&#10;(round-robin: primary &#8594; backup &#8594; primary...)"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;dashed=1;"
edge="1" parent="1" source="failover" target="retry">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="700" y="695" />
<mxPoint x="700" y="285" />
</Array>
</mxGeometry>
</mxCell>
<!-- reconnect -> Connected -->
<mxCell id="bdone" value="Transition to Connected"
style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;"
edge="1" parent="1" source="reconnect" target="connected">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="320" y="900" />
<mxPoint x="320" y="65" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

+2 -21
View File
@@ -547,27 +547,8 @@ This section governs how implementation plans are executed. The goal is autonomo
For each work package, follow this sequence:
```
┌─────────────────────────────────────────────────────┐
│ 1. READ the WP description and acceptance criteria │
│ 2. READ all traced requirements (HLR bullets, KDD, │
│ CD constraints) to understand intent │
│ 3. IMPLEMENT the WP │
│ - Write code │
│ - Write unit tests for acceptance criteria │
│ - Write negative tests for prohibition criteria │
│ 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" │
│ 5. UPDATE the phase execution checklist │
│ - Mark WP as complete with date │
│ - Note any deferred criteria │
│ - Note any questions logged │
│ 6. COMMIT with message: "Phase N WP-M: <summary>" │
└─────────────────────────────────────────────────────┘
```
![generate-plans-per-work-package](diagrams/generate-plans-per-work-package.png)
<!-- source: diagrams/generate-plans-per-work-package.drawio — edit, then re-export with export-drawio.sh -->
### Mid-Phase Compliance Check
+6 -60
View File
@@ -23,29 +23,8 @@ gRPC server-streaming is an established pattern for real-time tag value updates;
## Architecture
```
Central Cluster Site Cluster
───────────── ────────────
DebugStreamBridgeActor InstanceActor
│ │
│── SubscribeDebugView ──► │ (ClusterClient: command/control)
│◄── DebugViewSnapshot ── │
│ │
│ │ publishes AttributeValueChanged
│ │ publishes AlarmStateChanged
│ ▼
SiteStreamGrpcClient ◄──── gRPC stream ───── SiteStreamGrpcServer
(per-site, on central) (HTTP/2) (Kestrel, on site)
│ │
│ reads from gRPC stream │ receives from SiteStreamManager
│ routes by correlationId │ filters by instance name
▼ │
DebugStreamBridgeActor │
│ │
▼ │
SignalR Hub / Blazor UI │
```
![grpc-streams-architecture](diagrams/grpc-streams-architecture.png)
<!-- source: diagrams/grpc-streams-architecture.drawio — edit, then re-export with export-drawio.sh -->
**Key separation**: ClusterClient handles subscribe/unsubscribe/snapshot (request-response). gRPC handles the ongoing value stream (server-streaming).
@@ -271,17 +250,8 @@ public override async Task SubscribeInstance(
`IServerStreamWriter<T>` is **not thread-safe**. Multiple Akka actors may publish events concurrently. The `Channel<SiteStreamEvent>` bridges these worlds:
```
Akka Actor Thread(s) gRPC Response Stream
│ ▲
│ channel.Writer.TryWrite(evt) │ await responseStream.WriteAsync(evt)
▼ │
┌─────────────────────────────────────────┐
│ Channel<SiteStreamEvent> │
│ BoundedChannelOptions(1000) │
│ FullMode = DropOldest │
└─────────────────────────────────────────┘
```
![grpc-channel-bridging](diagrams/grpc-channel-bridging.png)
<!-- source: diagrams/grpc-channel-bridging.drawio — edit, then re-export with export-drawio.sh -->
- **Bounded capacity** (1000): prevents unbounded memory growth if the gRPC client is slow
- **DropOldest**: matches the existing `SiteStreamManager` overflow strategy
@@ -431,32 +401,8 @@ private void HandleGrpcStreamError(Exception ex)
### Reconnection State Machine (DebugStreamBridgeActor)
```
┌──────────────────┐
│ Streaming │ ◄── Normal state: gRPC stream active
└────────┬─────────┘
│ gRPC stream error / keepalive timeout
┌──────────────────┐
┌──► │ Reconnecting │ ── try other node endpoint
│ └────────┬─────────┘
│ │
│ ┌────────┴─────────┐
│ │ │
│ success failure (retry < max)
│ │ │
│ ▼ │
│ Streaming schedule retry (5s backoff)
│ │
└───────────────────────┘
failure (retry >= max)
┌──────────────────┐
│ Terminated │ ── notify consumer, stop actor
└──────────────────┘
```
![grpc-reconnection-state-machine](diagrams/grpc-reconnection-state-machine.png)
<!-- source: diagrams/grpc-reconnection-state-machine.drawio — edit, then re-export with export-drawio.sh -->
### Summary
+2 -14
View File
@@ -167,20 +167,8 @@ Keepalive settings are configurable via `CommunicationOptions`:
## Topology
```
Central Cluster
├── ClusterClient → Site A Cluster (SiteCommunicationActor via Receptionist) [command/control]
├── ClusterClient → Site B Cluster (SiteCommunicationActor via Receptionist) [command/control]
└── ClusterClient → Site N Cluster (SiteCommunicationActor via Receptionist) [command/control]
├── SiteStreamGrpcClient ◄── gRPC stream ── Site A (SiteStreamGrpcServer) [real-time data]
├── SiteStreamGrpcClient ◄── gRPC stream ── Site B (SiteStreamGrpcServer) [real-time data]
└── SiteStreamGrpcClient ◄── gRPC stream ── Site N (SiteStreamGrpcServer) [real-time data]
Site Clusters
└── ClusterClient → Central Cluster (CentralCommunicationActor via Receptionist) [command/control]
└── SiteStreamGrpcServer (Kestrel HTTP/2, port 8083) → serves gRPC streams [real-time data]
```
![communication-topology](diagrams/communication-topology.png)
<!-- source: diagrams/communication-topology.drawio — edit, then re-export with export-drawio.sh -->
- Sites do **not** communicate with each other.
- All inter-cluster communication flows through central.
@@ -143,15 +143,8 @@ EF Core's DbContext naturally provides unit-of-work semantics:
### Example Transactional Flow
```
Template Engine: Create Template
├── repository.AddTemplate(template) // template is a Commons POCO
├── repository.AddAttributes(attributes) // attributes are Commons POCOs
├── repository.AddAlarms(alarms) // alarms are Commons POCOs
├── repository.AddScripts(scripts) // scripts are Commons POCOs
└── repository.SaveChangesAsync() // single transaction commits all
```
![configdb-transactional-flow](diagrams/configdb-transactional-flow.png)
<!-- source: diagrams/configdb-transactional-flow.drawio — edit, then re-export with export-drawio.sh -->
---
@@ -184,14 +177,8 @@ Audit entries are written **synchronously** within the same database transaction
### Integration Example
```
Template Engine: Update Template
├── repository.UpdateTemplate(template)
├── auditService.LogAsync(user, "Update", "Template", template.Id,
│ template.Name, template)
└── repository.SaveChangesAsync() ← both the change and audit entry commit together
```
![configdb-integration-example](diagrams/configdb-integration-example.png)
<!-- source: diagrams/configdb-integration-example.drawio — edit, then re-export with export-drawio.sh -->
### Audit Entry Schema
@@ -80,12 +80,8 @@ Data connections support an optional backup endpoint for automatic failover when
**Failover state machine:**
```
Connected → disconnect → push bad quality → retry active endpoint (5s)
→ N failures (≥ FailoverRetryCount) → switch to other endpoint
→ dispose adapter, create fresh adapter with other config
→ reconnect → ReSubscribeAll → Connected
```
![dcl-endpoint-redundancy](diagrams/dcl-endpoint-redundancy.png)
<!-- source: diagrams/dcl-endpoint-redundancy.drawio — edit, then re-export with export-drawio.sh -->
- **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.
@@ -22,24 +22,8 @@ Central cluster only. The site-side deployment responsibilities (receiving confi
## Deployment Flow
```
Engineer (UI) → Deployment Manager (Central)
├── 1. Request validated + flattened config from Template Engine
│ (validation includes flattening, script compilation,
│ trigger references, connection binding completeness)
├── 2. If validation fails → return errors to UI, stop
├── 3. Send config to site via Communication Layer
│ │
│ ▼
│ Site Runtime (Deployment Manager Singleton)
│ ├── 4. Store new flattened config locally (SQLite)
│ ├── 5. Compile scripts at site
│ ├── 6. Create/update Instance Actor (with child Script + Alarm Actors)
│ └── 7. Report success/failure back to central
└── 8. Update deployment status in config DB
```
![deployment-flow](diagrams/deployment-flow.png)
<!-- source: diagrams/deployment-flow.drawio — edit, then re-export with export-drawio.sh -->
## Deployment Identity & Idempotency
+2 -14
View File
@@ -123,20 +123,8 @@ API method scripts are compiled at central startup — all method definitions ar
## Request Flow
```
External System
Inbound API (Central)
├── 1. Extract API key from request
├── 2. Validate key exists and is enabled
├── 3. Resolve method by name
├── 4. Check API key is in method's approved list
├── 5. Validate and deserialize parameters
├── 6. Execute implementation script (subject to method timeout)
├── 7. Serialize return value
└── 8. Return response
```
![inboundapi-request-flow](diagrams/inboundapi-request-flow.png)
<!-- source: diagrams/inboundapi-request-flow.drawio — edit, then re-export with export-drawio.sh -->
## Implementation Script Capabilities
@@ -24,24 +24,8 @@ SMTP and HTTP delivery is blocking I/O. Delivery work runs on a **dedicated bloc
## End-to-End Flow
```
Site script: Notify.To("list").Send(subject, body)
│ generate NotificationId (GUID) locally; return it to the script immediately
Site Store-and-Forward Engine (notification category, target = central)
│ durably forwards to central via CentralSite Communication (ClusterClient);
│ buffers/retries if central is unreachable
Central ingest: insert-if-not-exists on NotificationId → Notifications table (Pending)
│ ack the site → site S&F clears the message
Central Notification Outbox actor (singleton, active central node)
│ polls due rows; resolves the list; delivers via the matching adapter
├── success → Delivered
├── transient failure → Retrying (schedule NextAttemptAt)
└── permanent failure
/ retries exhausted → Parked
```
![notificationoutbox-flow](diagrams/notificationoutbox-flow.png)
<!-- source: diagrams/notificationoutbox-flow.drawio — edit, then re-export with export-drawio.sh -->
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.
+2 -15
View File
@@ -27,21 +27,8 @@ Site clusters only.
## Actor Hierarchy
```
Deployment Manager Singleton (Cluster Singleton)
├── Instance Actor ("MachineA-001")
│ ├── Script Actor ("MonitorSpeed") — coordinator
│ │ └── Script Execution Actor — short-lived, per invocation
│ ├── Script Actor ("CalculateOEE") — coordinator
│ │ └── Script Execution Actor — short-lived, per invocation
│ ├── Alarm Actor ("OverTemp") — coordinator (computed)
│ │ └── Alarm Execution Actor — short-lived, per on-trigger invocation
│ ├── Alarm Actor ("LowPressure") — coordinator (computed)
│ └── Native Alarm Actor ("OpcUaServer1") — read-only mirror, peer to Alarm Actor
├── Instance Actor ("MachineA-002")
│ └── ...
└── ...
```
![siteruntime-actor-hierarchy](diagrams/siteruntime-actor-hierarchy.png)
<!-- source: diagrams/siteruntime-actor-hierarchy.drawio — edit, then re-export with export-drawio.sh -->
---
+2 -17
View File
@@ -25,23 +25,8 @@ Site clusters only. The central cluster does not buffer messages.
## Message Lifecycle
```
Script submits message
Attempt immediate delivery
├── Success → Remove from buffer
└── Failure → Buffer message
Retry loop (per retry policy)
├── Success → Remove from buffer + notify standby
└── Max retries exhausted → Park message
```
![storeforward-message-lifecycle](diagrams/storeforward-message-lifecycle.png)
<!-- source: diagrams/storeforward-message-lifecycle.drawio — edit, then re-export with export-drawio.sh -->
For notifications, "delivery" means forwarding the message to the central cluster via CentralSite Communication; "success" is central's ack, on which the message is cleared. Notifications are retried at the fixed forward interval until central acks, but — like every other category — they are bounded by the engine's `DefaultMaxRetries` cap: a sustained central outage that exceeds `DefaultMaxRetries × forward-interval` will park the buffered notification, after which an operator can Retry/Discard it via the parked-message UI. Operationally, the cap is sized so the normal central-recovery window stays well inside it; "do not park" is the design's operational intent on the happy path, not an absolute invariant. Callers that genuinely require unbounded retry pass `maxRetries: 0` on `EnqueueAsync` (the documented "no limit" escape hatch — see `StoreAndForward-015`).
+6 -59
View File
@@ -92,20 +92,8 @@ The manifest is plaintext so the import wizard can preview bundle contents and s
## Architecture
```
ZB.MOM.WW.ScadaBridge.Transport
├── IBundleExporter
│ ExportAsync(ExportSelection, Passphrase?, ct) → Stream
├── IBundleImporter
│ LoadAsync(stream, Passphrase?, ct) → BundleSession
│ PreviewAsync(sessionId, ct) → ImportPreview
│ ApplyAsync(sessionId, resolutions, ct) → ImportResult
├── DependencyResolver
├── BundleSerializer (manifest + content JSON; ZIP packer)
├── BundleSecretEncryptor (AES-256-GCM + PBKDF2)
├── BundleSessionStore (in-memory, TTL'd)
└── ManifestValidator (schema/version gating, hash check)
```
![transport-architecture](diagrams/transport-architecture.png)
<!-- source: diagrams/transport-architecture.drawio — edit, then re-export with export-drawio.sh -->
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.
@@ -132,22 +120,8 @@ The user can toggle "include all dependencies" off (with a warning that the bund
### Backend
```
User (Design role) ─► Central UI Export wizard
IBundleExporter
├─► DependencyResolver ─► repositories (read)
├─► EntitySerializer ─► content.json
├─► BundleSecretEncryptor ► content.enc (if passphrase)
├─► ManifestBuilder ─► manifest.json
ZIP packer → temp file → browser download
IAuditService.LogAsync(BundleExported …)
```
![transport-export-flow](diagrams/transport-export-flow.png)
<!-- source: diagrams/transport-export-flow.drawio — edit, then re-export with export-drawio.sh -->
Audit event: `BundleExported` — caller, artifact count, content hash, encrypted yes/no, bundle filename.
@@ -179,35 +153,8 @@ Bundle references that cannot be satisfied in either the bundle or the target DB
### Backend
```
User (Admin role) ─► uploads bundle
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)
PreviewAsync → diff vs target DB → ImportPreview
▼ (user reviews + resolves conflicts)
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
ImportResult → UI step 5
"View on Deployments →" (existing page)
```
![transport-import-flow](diagrams/transport-import-flow.png)
<!-- source: diagrams/transport-import-flow.drawio — edit, then re-export with export-drawio.sh -->
Authorization: `RequireAdmin` on both the Razor page and `IBundleImporter.*` entrypoints.
@@ -0,0 +1,116 @@
<mxfile host="app.diagrams.net">
<diagram id="topology" name="Topology">
<mxGraphModel dx="1400" dy="900" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="1300" pageHeight="900" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Central Cluster container -->
<mxCell id="central" value="Central Cluster" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;verticalAlign=top;fontStyle=1;fontSize=14;" vertex="1" parent="1">
<mxGeometry x="60" y="60" width="380" height="320" as="geometry" />
</mxCell>
<mxCell id="cc-a" value="ClusterClient&#10;(command/control)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="90" y="110" width="150" height="50" as="geometry" />
</mxCell>
<mxCell id="cc-b" value="ClusterClient&#10;(command/control)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="90" y="170" width="150" height="50" as="geometry" />
</mxCell>
<mxCell id="cc-n" value="ClusterClient&#10;(command/control)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="90" y="230" width="150" height="50" as="geometry" />
</mxCell>
<mxCell id="grpc-client" value="SiteStreamGrpcClient&#10;(real-time data)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="90" y="300" width="150" height="50" as="geometry" />
</mxCell>
<!-- Site A -->
<mxCell id="siteA" value="Site A Cluster" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;verticalAlign=top;fontStyle=1;fontSize=14;" vertex="1" parent="1">
<mxGeometry x="720" y="60" width="380" height="250" as="geometry" />
</mxCell>
<mxCell id="siteA-comm" value="SiteCommunicationActor&#10;(via Receptionist)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="760" y="110" width="190" height="50" as="geometry" />
</mxCell>
<mxCell id="siteA-grpc" value="SiteStreamGrpcServer&#10;(Kestrel HTTP/2, port 8083)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="760" y="180" width="190" height="50" as="geometry" />
</mxCell>
<mxCell id="siteA-clusterclient" value="ClusterClient &#8594; Central&#10;(CentralCommunicationActor)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="760" y="245" width="190" height="50" as="geometry" />
</mxCell>
<!-- Site B -->
<mxCell id="siteB" value="Site B Cluster" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;verticalAlign=top;fontStyle=1;fontSize=14;" vertex="1" parent="1">
<mxGeometry x="720" y="370" width="380" height="130" as="geometry" />
</mxCell>
<mxCell id="siteB-comm" value="SiteCommunicationActor&#10;(via Receptionist)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="760" y="410" width="190" height="40" as="geometry" />
</mxCell>
<mxCell id="siteB-grpc" value="SiteStreamGrpcServer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="760" y="455" width="190" height="35" as="geometry" />
</mxCell>
<!-- Site N -->
<mxCell id="siteN" value="Site N Cluster" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;verticalAlign=top;fontStyle=1;fontSize=14;" vertex="1" parent="1">
<mxGeometry x="720" y="560" width="380" height="130" as="geometry" />
</mxCell>
<mxCell id="siteN-comm" value="SiteCommunicationActor&#10;(via Receptionist)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="760" y="600" width="190" height="40" as="geometry" />
</mxCell>
<mxCell id="siteN-grpc" value="SiteStreamGrpcServer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="760" y="645" width="190" height="35" as="geometry" />
</mxCell>
<!-- Command/control edges: Central ClusterClients -> Site comm actors -->
<mxCell id="e-cca" value="command/control" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;" edge="1" parent="1" source="cc-a" target="siteA-comm">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-ccb" value="command/control" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;" edge="1" parent="1" source="cc-b" target="siteB-comm">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-ccn" value="command/control" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;" edge="1" parent="1" source="cc-n" target="siteN-comm">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- gRPC stream edges: site servers -> central grpc client (data flows site->central) -->
<mxCell id="e-grpca" value="gRPC stream (real-time data)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="siteA-grpc" target="grpc-client">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="540" y="205" />
<mxPoint x="540" y="325" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-grpcb" value="gRPC stream" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="siteB-grpc" target="grpc-client">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="600" y="472" />
<mxPoint x="600" y="335" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e-grpcn" value="gRPC stream" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="siteN-grpc" target="grpc-client">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="660" y="662" />
<mxPoint x="660" y="345" />
</Array>
</mxGeometry>
</mxCell>
<!-- Site A -> Central command/control (ClusterClient to central) -->
<mxCell id="e-sa-central" value="command/control" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;dashed=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.7;entryDx=0;entryDy=0;" edge="1" parent="1" source="siteA-clusterclient" target="central">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="480" y="270" />
<mxPoint x="480" y="284" />
</Array>
</mxGeometry>
</mxCell>
<!-- Note: sites do not talk to each other -->
<mxCell id="note" value="Sites do NOT communicate with each other.&#10;All inter-cluster communication flows through Central." style="shape=note;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="60" y="560" width="380" height="80" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

@@ -0,0 +1,37 @@
<mxfile host="app.diagrams.net">
<diagram id="configdb-integration-example" name="Integration Example">
<mxGraphModel dx="800" dy="600" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="start" value="Template Engine: Update Template" style="rounded=1;arcSize=40;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="220" y="40" width="360" height="50" as="geometry" />
</mxCell>
<mxCell id="upd" value="repository.UpdateTemplate(template)" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="220" y="150" width="360" height="50" as="geometry" />
</mxCell>
<mxCell id="audit" value="auditService.LogAsync(user, &quot;Update&quot;, &quot;Template&quot;,&#10;&#160;&#160;&#160;&#160;template.Id, template.Name, template)" style="whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="220" y="230" width="360" height="60" as="geometry" />
</mxCell>
<mxCell id="save" value="repository.SaveChangesAsync()" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="220" y="330" width="360" height="50" as="geometry" />
</mxCell>
<mxCell id="note" value="both the change and audit entry&#10;commit together" style="shape=note;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;size=14;align=left;spacingLeft=10;" vertex="1" parent="1">
<mxGeometry x="630" y="330" width="180" height="50" as="geometry" />
</mxCell>
<mxCell id="e0" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="start" target="upd">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="upd" target="audit">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="audit" target="save">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=none;dashed=1;" edge="1" parent="1" source="save" target="note">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

@@ -0,0 +1,49 @@
<mxfile host="app.diagrams.net">
<diagram id="configdb-transactional-flow" name="Transactional Flow">
<mxGraphModel dx="800" dy="600" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="start" value="Template Engine: Create Template" style="rounded=1;arcSize=40;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="240" y="40" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="add1" value="repository.AddTemplate(template)&#10;// template is a Commons POCO" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="240" y="150" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="add2" value="repository.AddAttributes(attributes)&#10;// attributes are Commons POCOs" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="240" y="230" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="add3" value="repository.AddAlarms(alarms)&#10;// alarms are Commons POCOs" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="240" y="310" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="add4" value="repository.AddScripts(scripts)&#10;// scripts are Commons POCOs" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="240" y="390" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="save" value="repository.SaveChangesAsync()&#10;// single transaction commits all" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="240" y="480" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="db" value="Configuration DB&#10;(MS SQL)" style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="640" y="475" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="e0" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="start" target="add1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="add1" target="add2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="add2" target="add3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="add3" target="add4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="add4" target="save">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" value="single transaction" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;dashed=1;" edge="1" parent="1" source="save" target="db">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

@@ -0,0 +1,71 @@
<mxfile host="app.diagrams.net">
<diagram id="dcl-endpoint-redundancy" name="Failover State Machine">
<mxGraphModel dx="900" dy="700" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="connected" value="Connected" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="360" y="40" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="pushbad" value="push bad quality" style="whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="360" y="150" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="retry" value="retry active endpoint&#10;(5s)" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="360" y="250" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="decide" value="N failures&#10;(&#8805; FailoverRetryCount)?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="350" y="350" width="180" height="100" as="geometry" />
</mxCell>
<mxCell id="switch" value="switch to other endpoint" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
<mxGeometry x="360" y="500" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="dispose" value="dispose adapter,&#10;create fresh adapter&#10;with other config" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
<mxGeometry x="360" y="600" width="160" height="60" as="geometry" />
</mxCell>
<mxCell id="reconnect" value="reconnect" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="360" y="710" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="resub" value="ReSubscribeAll" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="360" y="800" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="e0" value="disconnect" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="connected" target="pushbad">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="pushbad" target="retry">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="retry" target="decide">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2b" value="no (retry again)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="decide" target="retry">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="620" y="400" />
<mxPoint x="620" y="275" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e3" value="yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="decide" target="switch">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="switch" target="dispose">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="dispose" target="reconnect">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="reconnect" target="resub">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e7" value="back to Connected" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="resub" target="connected">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="240" y="825" />
<mxPoint x="240" y="65" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

@@ -0,0 +1,96 @@
<mxfile host="app.diagrams.net">
<diagram id="deployflow" name="Deployment Flow">
<mxGraphModel dx="1200" dy="1400" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="1500" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Start: Engineer -->
<mxCell id="engineer" value="Engineer (UI)" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="320" y="40" width="200" height="50" as="geometry" />
</mxCell>
<!-- Deployment Manager (Central) container header -->
<mxCell id="dm-central" value="Deployment Manager (Central)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;verticalAlign=top;fontStyle=1;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="120" y="130" width="640" height="430" as="geometry" />
</mxCell>
<mxCell id="step1" value="1. Request validated + flattened config from Template Engine&#10;(validation: flattening, script compilation, trigger references,&#10;connection binding completeness)" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="160" y="180" width="560" height="60" as="geometry" />
</mxCell>
<mxCell id="step2" value="2. Validation fails?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="350" y="270" width="180" height="80" as="geometry" />
</mxCell>
<mxCell id="step2fail" value="Return errors to UI, stop" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="600" y="280" width="140" height="60" as="geometry" />
</mxCell>
<mxCell id="step3" value="3. Send config to site via Communication Layer" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="220" y="390" width="440" height="50" as="geometry" />
</mxCell>
<mxCell id="step8" value="8. Update deployment status in config DB" style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="280" y="480" width="320" height="70" as="geometry" />
</mxCell>
<!-- Site Runtime container -->
<mxCell id="site-runtime" value="Site Runtime (Deployment Manager Singleton)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;verticalAlign=top;fontStyle=1;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="120" y="620" width="640" height="380" as="geometry" />
</mxCell>
<mxCell id="step4" value="4. Store new flattened config locally (SQLite)" style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="200" y="670" width="480" height="70" as="geometry" />
</mxCell>
<mxCell id="step5" value="5. Compile scripts at site" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="240" y="770" width="400" height="50" as="geometry" />
</mxCell>
<mxCell id="step6" value="6. Create/update Instance Actor&#10;(with child Script + Alarm Actors)" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="240" y="850" width="400" height="60" as="geometry" />
</mxCell>
<mxCell id="step7" value="7. Report success/failure back to central" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="240" y="940" width="400" height="50" as="geometry" />
</mxCell>
<!-- Edges -->
<mxCell id="e0" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="engineer" target="step1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="step1" target="step2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2yes" value="yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="step2" target="step2fail">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2no" value="no" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="step2" target="step3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" value="config" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="step3" target="step4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="step4" target="step5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="step5" target="step6">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="step6" target="step7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e7" value="report success/failure" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;dashed=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="step7" target="step8">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="780" y="965" />
<mxPoint x="780" y="515" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

@@ -0,0 +1,67 @@
<mxfile host="app.diagrams.net">
<diagram id="inboundapi-request-flow" name="Request Flow">
<mxGraphModel dx="900" dy="900" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="ext" value="External System" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="320" y="40" width="200" height="50" as="geometry" />
</mxCell>
<mxCell id="api" value="Inbound API (Central)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="320" y="140" width="200" height="50" as="geometry" />
</mxCell>
<mxCell id="s1" value="1. Extract API key from request" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="280" y="240" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="s2" value="2. Validate key exists and is enabled" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="280" y="300" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="s3" value="3. Resolve method by name" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="280" y="360" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="s4" value="4. Check API key is in method's approved list" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="280" y="420" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="s5" value="5. Validate and deserialize parameters" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="280" y="480" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="s6" value="6. Execute implementation script&#10;(subject to method timeout)" style="whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="280" y="540" width="280" height="50" as="geometry" />
</mxCell>
<mxCell id="s7" value="7. Serialize return value" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;" vertex="1" parent="1">
<mxGeometry x="280" y="610" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="s8" value="8. Return response" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;spacingLeft=12;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="280" y="670" width="280" height="40" as="geometry" />
</mxCell>
<mxCell id="e0" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="ext" target="api">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="api" target="s1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s1" target="s2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s2" target="s3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s3" target="s4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s4" target="s5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s5" target="s6">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s6" target="s7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="s7" target="s8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

@@ -0,0 +1,83 @@
<mxfile host="app.diagrams.net">
<diagram id="outboxflow" name="End-to-End Flow">
<mxGraphModel dx="1000" dy="1400" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="950" pageHeight="1400" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Site script -->
<mxCell id="script" value="Site script: Notify.To(&quot;list&quot;).Send(subject, body)&#10;generate NotificationId (GUID) locally;&#10;return it to the script immediately" style="rounded=1;arcSize=20;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="280" y="40" width="380" height="70" as="geometry" />
</mxCell>
<!-- Site S&F Engine -->
<mxCell id="snf" value="Site Store-and-Forward Engine&#10;(notification category, target = central)&#10;durably forwards to central via CentralSite Communication&#10;(ClusterClient); buffers/retries if central is unreachable" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="240" y="170" width="460" height="80" as="geometry" />
</mxCell>
<!-- Central ingest -->
<mxCell id="ingest" value="Central ingest: insert-if-not-exists on NotificationId&#10;→ Notifications table (Pending)&#10;ack the site → site S&amp;F clears the message" style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="270" y="310" width="400" height="90" as="geometry" />
</mxCell>
<!-- Central Notification Outbox actor -->
<mxCell id="outbox" value="Central Notification Outbox actor&#10;(singleton, active central node)&#10;polls due rows; resolves the list;&#10;delivers via the matching adapter" style="whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="280" y="460" width="380" height="80" as="geometry" />
</mxCell>
<!-- Delivery decision -->
<mxCell id="d1" value="Delivery outcome" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="400" y="610" width="140" height="100" as="geometry" />
</mxCell>
<!-- Delivered -->
<mxCell id="delivered" value="Delivered" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="120" y="630" width="180" height="60" as="geometry" />
</mxCell>
<!-- Retrying -->
<mxCell id="retrying" value="Retrying&#10;(schedule NextAttemptAt)" style="rounded=1;arcSize=20;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="380" y="790" width="180" height="60" as="geometry" />
</mxCell>
<!-- Parked -->
<mxCell id="parked" value="Parked" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="650" y="630" width="180" height="60" as="geometry" />
</mxCell>
<!-- Edges -->
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="script" target="snf">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="snf" target="ingest">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="ingest" target="outbox">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="outbox" target="d1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" value="success" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="d1" target="delivered">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" value="transient failure" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="d1" target="retrying">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e7" value="permanent failure /&#10;retries exhausted" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="d1" target="parked">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Retrying loops back to outbox poll -->
<mxCell id="e8" value="retry due" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=11;dashed=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="retrying" target="outbox">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="780" y="820" />
<mxPoint x="780" y="500" />
</Array>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

@@ -0,0 +1,104 @@
<mxfile host="app.diagrams.net">
<diagram id="actorhierarchy" name="Actor Hierarchy">
<mxGraphModel dx="1400" dy="1000" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="1320" pageHeight="560" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Root: Deployment Manager Singleton -->
<mxCell id="dms" value="Deployment Manager Singleton&#10;(Cluster Singleton)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="490" y="30" width="280" height="60" as="geometry" />
</mxCell>
<!-- Instance Actors -->
<mxCell id="ia1" value="Instance Actor&#10;(&quot;MachineA-001&quot;)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="368" y="160" width="184" height="60" as="geometry" />
</mxCell>
<mxCell id="ia2" value="Instance Actor&#10;(&quot;MachineA-002&quot;)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="980" y="160" width="184" height="60" as="geometry" />
</mxCell>
<mxCell id="iamore" value="… more Instance Actors" style="text;html=1;align=center;verticalAlign=middle;fontSize=12;fontStyle=2;" vertex="1" parent="1">
<mxGeometry x="1190" y="170" width="120" height="40" as="geometry" />
</mxCell>
<!-- Children of MachineA-001 (top row) -->
<mxCell id="sa1" value="Script Actor (&quot;MonitorSpeed&quot;)&#10;— coordinator" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="16" y="300" width="164" height="74" as="geometry" />
</mxCell>
<mxCell id="sa2" value="Script Actor (&quot;CalculateOEE&quot;)&#10;— coordinator" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="196" y="300" width="164" height="74" as="geometry" />
</mxCell>
<mxCell id="aa1" value="Alarm Actor (&quot;OverTemp&quot;)&#10;— coordinator (computed)" style="whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="376" y="300" width="164" height="74" as="geometry" />
</mxCell>
<mxCell id="aa2" value="Alarm Actor (&quot;LowPressure&quot;)&#10;— coordinator (computed)" style="whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="556" y="300" width="164" height="74" as="geometry" />
</mxCell>
<mxCell id="naa1" value="Native Alarm Actor (&quot;OpcUaServer1&quot;)&#10;— read-only mirror, peer to Alarm Actor" style="whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="736" y="300" width="164" height="74" as="geometry" />
</mxCell>
<!-- Execution actors (bottom row, under their coordinators) -->
<mxCell id="sea1" value="Script Execution Actor&#10;— short-lived, per invocation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;dashed=1;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="16" y="430" width="164" height="64" as="geometry" />
</mxCell>
<mxCell id="sea2" value="Script Execution Actor&#10;— short-lived, per invocation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;dashed=1;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="196" y="430" width="164" height="64" as="geometry" />
</mxCell>
<mxCell id="aea1" value="Alarm Execution Actor&#10;— short-lived, per on-trigger invocation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;dashed=1;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="376" y="430" width="164" height="64" as="geometry" />
</mxCell>
<!-- Child of MachineA-002 -->
<mxCell id="ia2child" value="… (Script / Alarm Actors)" style="text;html=1;align=center;verticalAlign=middle;fontSize=12;fontStyle=2;" vertex="1" parent="1">
<mxGeometry x="992" y="304" width="160" height="40" as="geometry" />
</mxCell>
<!-- Edges: root -> instances -->
<mxCell id="e-dms-ia1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="dms" target="ia1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-dms-ia2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="dms" target="ia2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-dms-more" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;dashed=1;" edge="1" parent="1" source="dms" target="iamore">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Edges: MachineA-001 -> its children (all top-row, no box in between) -->
<mxCell id="e-ia1-sa1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="ia1" target="sa1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-ia1-sa2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="ia1" target="sa2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-ia1-aa1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="ia1" target="aa1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-ia1-aa2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="ia1" target="aa2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-ia1-naa1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="ia1" target="naa1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Edges: coordinator -> execution actor (short vertical, own column) -->
<mxCell id="e-sa1-sea1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="sa1" target="sea1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-sa2-sea2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="sa2" target="sea2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e-aa1-aea1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="aa1" target="aea1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<!-- Edge: MachineA-002 -> its child -->
<mxCell id="e-ia2-child" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;dashed=1;" edge="1" parent="1" source="ia2" target="ia2child">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

@@ -0,0 +1,82 @@
<mxfile host="app.diagrams.net">
<diagram id="msglifecycle" name="Message Lifecycle">
<mxGraphModel dx="1000" dy="1300" grid="1" gridSize="10" guides="1" arrows="1"
fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="1300" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<!-- Start -->
<mxCell id="submit" value="Script submits message" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="320" y="40" width="220" height="50" as="geometry" />
</mxCell>
<!-- Attempt immediate delivery -->
<mxCell id="attempt" value="Attempt immediate delivery" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="320" y="140" width="220" height="50" as="geometry" />
</mxCell>
<!-- Decision: success or failure -->
<mxCell id="d1" value="Delivered?" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="350" y="240" width="160" height="90" as="geometry" />
</mxCell>
<!-- Success -> Remove from buffer -->
<mxCell id="remove1" value="Remove from buffer" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="620" y="255" width="200" height="60" as="geometry" />
</mxCell>
<!-- Failure -> Buffer message -->
<mxCell id="buffer" value="Buffer message" style="whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="330" y="390" width="200" height="50" as="geometry" />
</mxCell>
<!-- Retry loop -->
<mxCell id="retry" value="Retry loop&#10;(per retry policy)" style="whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="330" y="490" width="200" height="60" as="geometry" />
</mxCell>
<!-- Retry decision -->
<mxCell id="d2" value="Retry outcome" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="350" y="610" width="160" height="100" as="geometry" />
</mxCell>
<!-- Retry success -> Remove from buffer + notify standby -->
<mxCell id="remove2" value="Remove from buffer&#10;+ notify standby" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="620" y="625" width="200" height="70" as="geometry" />
</mxCell>
<!-- Max retries exhausted -> Park message -->
<mxCell id="park" value="Park message&#10;(dead-letter)" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="330" y="770" width="200" height="60" as="geometry" />
</mxCell>
<!-- Edges -->
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="submit" target="attempt">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="attempt" target="d1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" value="Success" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="d1" target="remove1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" value="Failure" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="d1" target="buffer">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="buffer" target="retry">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="retry" target="d2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e7" value="Success" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="d2" target="remove2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e8" value="Max retries exhausted" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;fontSize=12;" edge="1" parent="1" source="d2" target="park">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

@@ -0,0 +1,34 @@
<mxfile host="app.diagrams.net">
<diagram id="transport-architecture" name="Architecture">
<mxGraphModel dx="900" dy="800" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="container" value="ZB.MOM.WW.ScadaBridge.Transport" style="swimlane;whiteSpace=wrap;html=1;startSize=34;fillColor=#f5f5f5;strokeColor=#666666;fontStyle=1;fontSize=14;verticalAlign=top;" vertex="1" parent="1">
<mxGeometry x="80" y="40" width="560" height="610" as="geometry" />
</mxCell>
<mxCell id="exporter" value="IBundleExporter&#10;ExportAsync(ExportSelection, Passphrase?, ct) → Stream" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;verticalAlign=top;spacingTop=8;" vertex="1" parent="container">
<mxGeometry x="30" y="50" width="500" height="70" as="geometry" />
</mxCell>
<mxCell id="importer" value="IBundleImporter&#10;LoadAsync(stream, Passphrase?, ct) → BundleSession&#10;PreviewAsync(sessionId, ct) → ImportPreview&#10;ApplyAsync(sessionId, resolutions, ct) → ImportResult" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;verticalAlign=top;spacingTop=8;" vertex="1" parent="container">
<mxGeometry x="30" y="140" width="500" height="110" as="geometry" />
</mxCell>
<mxCell id="resolver" value="DependencyResolver" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;spacingLeft=12;" vertex="1" parent="container">
<mxGeometry x="30" y="270" width="500" height="40" as="geometry" />
</mxCell>
<mxCell id="serializer" value="BundleSerializer&#10;(manifest + content JSON; ZIP packer)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;align=left;spacingLeft=12;" vertex="1" parent="container">
<mxGeometry x="30" y="330" width="500" height="40" as="geometry" />
</mxCell>
<mxCell id="encryptor" value="BundleSecretEncryptor&#10;(AES-256-GCM + PBKDF2)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;spacingLeft=12;" vertex="1" parent="container">
<mxGeometry x="30" y="390" width="500" height="40" as="geometry" />
</mxCell>
<mxCell id="sessionstore" value="BundleSessionStore&#10;(in-memory, TTL'd)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;align=left;spacingLeft=12;" vertex="1" parent="container">
<mxGeometry x="30" y="450" width="500" height="40" as="geometry" />
</mxCell>
<mxCell id="manifestvalidator" value="ManifestValidator&#10;(schema/version gating, hash check)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;align=left;spacingLeft=12;" vertex="1" parent="container">
<mxGeometry x="30" y="510" width="500" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

@@ -0,0 +1,85 @@
<mxfile host="app.diagrams.net">
<diagram id="transport-export-flow" name="Export Flow (Backend)">
<mxGraphModel dx="1000" dy="800" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="user" value="User (Design role)" style="rounded=1;arcSize=50;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="160" height="50" as="geometry" />
</mxCell>
<mxCell id="wizard" value="Central UI Export wizard" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="280" y="40" width="200" height="50" as="geometry" />
</mxCell>
<mxCell id="exporter" value="IBundleExporter" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="280" y="150" width="200" height="50" as="geometry" />
</mxCell>
<mxCell id="resolver" value="DependencyResolver" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="280" y="250" width="200" height="40" as="geometry" />
</mxCell>
<mxCell id="repos" value="repositories (read)" style="shape=cylinder3;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" vertex="1" parent="1">
<mxGeometry x="600" y="245" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="serializer" value="EntitySerializer" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="280" y="310" width="200" height="40" as="geometry" />
</mxCell>
<mxCell id="contentjson" value="content.json" style="shape=note;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;size=12;" vertex="1" parent="1">
<mxGeometry x="610" y="310" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="encryptor" value="BundleSecretEncryptor" style="whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
<mxGeometry x="280" y="370" width="200" height="40" as="geometry" />
</mxCell>
<mxCell id="contentenc" value="content.enc&#10;(if passphrase)" style="shape=note;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;size=12;" vertex="1" parent="1">
<mxGeometry x="610" y="368" width="110" height="44" as="geometry" />
</mxCell>
<mxCell id="manifestbuilder" value="ManifestBuilder" style="whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="280" y="430" width="200" height="40" as="geometry" />
</mxCell>
<mxCell id="manifestjson" value="manifest.json" style="shape=note;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;size=12;" vertex="1" parent="1">
<mxGeometry x="610" y="430" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="zip" value="ZIP packer → temp file → browser download" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="240" y="520" width="280" height="50" as="geometry" />
</mxCell>
<mxCell id="audit" value="IAuditService.LogAsync(BundleExported …)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="240" y="620" width="280" height="50" as="geometry" />
</mxCell>
<mxCell id="e0" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="user" target="wizard">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="wizard" target="exporter">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="exporter" target="resolver">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="resolver" target="serializer">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="serializer" target="encryptor">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="encryptor" target="manifestbuilder">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="manifestbuilder" target="zip">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="zip" target="audit">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="b1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="resolver" target="repos">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="b2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="serializer" target="contentjson">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="b3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="encryptor" target="contentenc">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="b4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="manifestbuilder" target="manifestjson">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

@@ -0,0 +1,49 @@
<mxfile host="app.diagrams.net">
<diagram id="transport-import-flow" name="Import Flow (Backend)">
<mxGraphModel dx="1000" dy="900" grid="1" gridSize="10" guides="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="900" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="user" value="User (Admin role) ─► uploads bundle" style="rounded=1;arcSize=30;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="260" y="40" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="load" value="IBundleImporter.LoadAsync&#10;&#160;&#160;· verify SHA-256 (manifest vs content)&#10;&#160;&#160;· check bundleFormatVersion supported&#10;&#160;&#160;· decrypt content.enc with passphrase (if encrypted)&#10;&#160;&#160;· deserialize entities&#10;&#160;&#160;· open BundleSession (30-min TTL)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;align=left;spacingLeft=12;verticalAlign=top;spacingTop=8;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="220" y="150" width="400" height="140" as="geometry" />
</mxCell>
<mxCell id="preview" value="PreviewAsync → diff vs target DB → ImportPreview" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="220" y="350" width="400" height="50" as="geometry" />
</mxCell>
<mxCell id="review" value="(user reviews + resolves conflicts)" style="whiteSpace=wrap;html=1;fillColor=none;strokeColor=none;fontStyle=2;align=left;" vertex="1" parent="1">
<mxGeometry x="630" y="425" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="apply" value="ApplyAsync (single EF transaction)&#10;&#160;&#160;· run two-tier semantic validation&#10;&#160;&#160;&#160;&#160;(minimal name scan + full SemanticValidator)&#10;&#160;&#160;· apply resolutions (add / overwrite / skip / rename)&#10;&#160;&#160;· upsert TemplateFolder hierarchy&#10;&#160;&#160;· IAuditService.LogAsync(BundleImported …)&#10;&#160;&#160;· commit" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;align=left;spacingLeft=12;verticalAlign=top;spacingTop=8;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="220" y="460" width="400" height="160" as="geometry" />
</mxCell>
<mxCell id="result" value="ImportResult → UI step 5" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="260" y="680" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="deployments" value="&quot;View on Deployments →&quot; (existing page)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontStyle=1;" vertex="1" parent="1">
<mxGeometry x="260" y="780" width="320" height="50" as="geometry" />
</mxCell>
<mxCell id="e0" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="user" target="load">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="load" target="preview">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="preview" target="apply">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e2b" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=none;dashed=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="preview" target="review">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="apply" target="result">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;" edge="1" parent="1" source="result" target="deployments">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB