refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

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

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
+173 -173
View File
@@ -4,19 +4,19 @@
**Goal:** Ship Component #24 (Transport) — file-based, encrypted bundle export/import of templates, system artifacts, and central-only configuration via the Central UI — as a single shipping slice.
**Architecture:** New `ScadaLink.Transport` project peer to Template Engine + Deployment Manager. Two new Central UI pages under the Design nav group. Persistence flows through existing audited repositories with a new scoped `IAuditCorrelationContext` carrying `BundleImportId`. AES-256-GCM + PBKDF2-SHA256 (600 000 iterations) for content encryption. No site changes.
**Architecture:** New `ZB.MOM.WW.ScadaBridge.Transport` project peer to Template Engine + Deployment Manager. Two new Central UI pages under the Design nav group. Persistence flows through existing audited repositories with a new scoped `IAuditCorrelationContext` carrying `BundleImportId`. AES-256-GCM + PBKDF2-SHA256 (600 000 iterations) for content encryption. No site changes.
**Tech Stack:** .NET 10, EF Core, ASP.NET Core / Blazor Server, xUnit + NSubstitute + FluentAssertions, in-memory EF for integration tests, existing `TreeView.razor` component extended with a checkbox-selection mode.
**Source design doc:** `docs/plans/2026-05-24-transport-design.md` (already committed).
**Key codebase facts that shape this plan:**
- Audit entity is `AuditLogEntry` (`src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs`), DbSet `_context.AuditLogEntries`.
- Audit entity is `AuditLogEntry` (`src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/AuditLogEntry.cs`), DbSet `_context.AuditLogEntries`.
- `IAuditService.LogAsync(user, action, entityType, entityId, entityName, afterState, ct)` — no existing correlation parameter; we add a scoped `IAuditCorrelationContext` rather than changing every repository signature.
- **No persisted `DeployedRevisionHash` on Instance.** `DeploymentService.CompareAsync` computes stale-vs-fresh live by comparing `snapshot.RevisionHash` to the current flattened-config hash. Overwriting a template naturally changes the hash, so the Deployments page surfaces affected instances automatically — **no explicit stale-mark write is required.**
- `TreeView.razor` exists at `src/ScadaLink.CentralUI/Components/Shared/TreeView.razor` with single-select navigation. We extend it with a checkbox / multi-select mode rather than building a parallel widget.
- DI extension convention: `AddTransport()` in `ServiceCollectionExtensions.cs`, registered in `src/ScadaLink.Host/Program.cs` under "Central-only components".
- Test integration pattern: `ScadaLinkWebApplicationFactory : WebApplicationFactory<Program>` with in-memory EF.
- `TreeView.razor` exists at `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TreeView.razor` with single-select navigation. We extend it with a checkbox / multi-select mode rather than building a parallel widget.
- DI extension convention: `AddTransport()` in `ServiceCollectionExtensions.cs`, registered in `src/ZB.MOM.WW.ScadaBridge.Host/Program.cs` under "Central-only components".
- Test integration pattern: `ScadaBridgeWebApplicationFactory : WebApplicationFactory<Program>` with in-memory EF.
**No feature flag** — backward compatible additive change; users who never open the new pages see no behavior difference.
@@ -32,7 +32,7 @@
| 3 | EF migration `AddBundleImportIdToAuditLog` | trivial | ~2 min |
| 4 | `IAuditCorrelationContext` scoped service | small | ~3 min |
| 5 | `AuditService` reads correlation context | small | ~3 min |
| 6 | `ScadaLink.Transport` project skeleton + slnx + DI | small | ~3 min |
| 6 | `ZB.MOM.WW.ScadaBridge.Transport` project skeleton + slnx + DI | small | ~3 min |
| 7 | `TransportOptions` | trivial | ~2 min |
| 8 | `BundleSecretEncryptor` (AES-256-GCM + PBKDF2) + tests | standard | ~4 min |
| 9 | `ManifestBuilder` + manifest schema validation | standard | ~4 min |
@@ -66,16 +66,16 @@
**Parallelizable with:** Task 1, Task 2, Task 4, Task 19
**Files:**
- Create: `src/ScadaLink.Commons/Types/Transport/BundleManifest.cs`
- Create: `src/ScadaLink.Commons/Types/Transport/BundleSummary.cs`
- Create: `src/ScadaLink.Commons/Types/Transport/EncryptionMetadata.cs`
- Create: `src/ScadaLink.Commons/Types/Transport/ManifestContentEntry.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/BundleManifest.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/BundleSummary.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/EncryptionMetadata.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/ManifestContentEntry.cs`
**Step 1: Write the DTOs**
`BundleManifest.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public sealed record BundleManifest(
int BundleFormatVersion,
@@ -83,7 +83,7 @@ public sealed record BundleManifest(
DateTimeOffset CreatedAtUtc,
string SourceEnvironment,
string ExportedBy,
string ScadaLinkVersion,
string ScadaBridgeVersion,
string ContentHash,
EncryptionMetadata? Encryption,
BundleSummary Summary,
@@ -92,7 +92,7 @@ public sealed record BundleManifest(
`EncryptionMetadata.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public sealed record EncryptionMetadata(
string Algorithm, // "AES-256-GCM"
@@ -104,7 +104,7 @@ public sealed record EncryptionMetadata(
`BundleSummary.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public sealed record BundleSummary(
int Templates,
@@ -120,7 +120,7 @@ public sealed record BundleSummary(
`ManifestContentEntry.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public sealed record ManifestContentEntry(
string Type,
@@ -131,13 +131,13 @@ public sealed record ManifestContentEntry(
**Step 2: Build**
Run: `dotnet build src/ScadaLink.Commons/ScadaLink.Commons.csproj`
Run: `dotnet build src/ZB.MOM.WW.ScadaBridge.Commons/ZB.MOM.WW.ScadaBridge.Commons.csproj`
Expected: build succeeds.
**Step 3: Commit**
```bash
git add src/ScadaLink.Commons/Types/Transport/
git add src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/
git commit -m "feat(transport): add bundle manifest DTOs in Commons"
```
@@ -150,21 +150,21 @@ git commit -m "feat(transport): add bundle manifest DTOs in Commons"
**Parallelizable with:** Task 0, Task 2, Task 4, Task 19
**Files:**
- Create: `src/ScadaLink.Commons/Types/Transport/ExportSelection.cs`
- Create: `src/ScadaLink.Commons/Types/Transport/ImportPreview.cs`
- Create: `src/ScadaLink.Commons/Types/Transport/ImportResolution.cs`
- Create: `src/ScadaLink.Commons/Types/Transport/ImportResult.cs`
- Create: `src/ScadaLink.Commons/Types/Transport/BundleSession.cs`
- Create: `src/ScadaLink.Commons/Interfaces/Transport/IBundleExporter.cs`
- Create: `src/ScadaLink.Commons/Interfaces/Transport/IBundleImporter.cs`
- Create: `src/ScadaLink.Commons/Interfaces/Transport/IBundleSessionStore.cs`
- Create: `src/ScadaLink.Commons/Interfaces/Transport/IAuditCorrelationContext.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/ExportSelection.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/ImportPreview.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/ImportResolution.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/ImportResult.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/BundleSession.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Transport/IBundleExporter.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Transport/IBundleImporter.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Transport/IBundleSessionStore.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Transport/IAuditCorrelationContext.cs`
**Step 1: Write the types and interfaces**
`ExportSelection.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public sealed record ExportSelection(
IReadOnlyList<int> TemplateIds,
@@ -180,7 +180,7 @@ public sealed record ExportSelection(
`ImportPreview.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public enum ConflictKind { Identical, Modified, New, Blocker }
@@ -200,7 +200,7 @@ public sealed record ImportPreview(
`ImportResolution.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public enum ResolutionAction { Add, Overwrite, Skip, Rename }
@@ -213,7 +213,7 @@ public sealed record ImportResolution(
`ImportResult.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public sealed record ImportResult(
Guid BundleImportId,
@@ -227,7 +227,7 @@ public sealed record ImportResult(
`BundleSession.cs`:
```csharp
namespace ScadaLink.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
public sealed class BundleSession
{
@@ -242,9 +242,9 @@ public sealed class BundleSession
`IBundleExporter.cs`:
```csharp
using ScadaLink.Commons.Types.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
namespace ScadaLink.Commons.Interfaces.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
public interface IBundleExporter
{
@@ -259,9 +259,9 @@ public interface IBundleExporter
`IBundleImporter.cs`:
```csharp
using ScadaLink.Commons.Types.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
namespace ScadaLink.Commons.Interfaces.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
public interface IBundleImporter
{
@@ -277,9 +277,9 @@ public interface IBundleImporter
`IBundleSessionStore.cs`:
```csharp
using ScadaLink.Commons.Types.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
namespace ScadaLink.Commons.Interfaces.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
public interface IBundleSessionStore
{
@@ -292,7 +292,7 @@ public interface IBundleSessionStore
`IAuditCorrelationContext.cs`:
```csharp
namespace ScadaLink.Commons.Interfaces.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
/// <summary>
/// Scoped service the bundle importer sets to thread a BundleImportId through to
@@ -307,13 +307,13 @@ public interface IAuditCorrelationContext
**Step 2: Build**
Run: `dotnet build src/ScadaLink.Commons/ScadaLink.Commons.csproj`
Run: `dotnet build src/ZB.MOM.WW.ScadaBridge.Commons/ZB.MOM.WW.ScadaBridge.Commons.csproj`
Expected: build succeeds.
**Step 3: Commit**
```bash
git add src/ScadaLink.Commons/Types/Transport/ src/ScadaLink.Commons/Interfaces/Transport/
git add src/ZB.MOM.WW.ScadaBridge.Commons/Types/Transport/ src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Transport/
git commit -m "feat(transport): add IBundleExporter / IBundleImporter interfaces"
```
@@ -326,8 +326,8 @@ git commit -m "feat(transport): add IBundleExporter / IBundleImporter interfaces
**Parallelizable with:** Task 0, Task 1, Task 19
**Files:**
- Modify: `src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs` (add nullable `BundleImportId`)
- Modify: `src/ScadaLink.ConfigurationDatabase/ScadaLinkDbContext.cs` (add Fluent config in `OnModelCreating` near the existing `AuditLogEntry` mapping; add `IX_AuditLogEntries_BundleImportId` non-clustered index)
- Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/AuditLogEntry.cs` (add nullable `BundleImportId`)
- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs` (add Fluent config in `OnModelCreating` near the existing `AuditLogEntry` mapping; add `IX_AuditLogEntries_BundleImportId` non-clustered index)
**Step 1: Add property**
@@ -338,7 +338,7 @@ public Guid? BundleImportId { get; set; }
**Step 2: Add Fluent config + index**
In `ScadaLinkDbContext.OnModelCreating`, find the existing `modelBuilder.Entity<AuditLogEntry>(...)` block and add:
In `ScadaBridgeDbContext.OnModelCreating`, find the existing `modelBuilder.Entity<AuditLogEntry>(...)` block and add:
```csharp
e.HasIndex(x => x.BundleImportId).HasDatabaseName("IX_AuditLogEntries_BundleImportId");
```
@@ -347,14 +347,14 @@ If no existing block, add one near other audit configs.
**Step 3: Build**
Run: `dotnet build src/ScadaLink.ConfigurationDatabase/ScadaLink.ConfigurationDatabase.csproj`
Run: `dotnet build src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.csproj`
Expected: build succeeds.
**Step 4: Commit**
```bash
git add src/ScadaLink.Commons/Entities/Audit/AuditLogEntry.cs \
src/ScadaLink.ConfigurationDatabase/ScadaLinkDbContext.cs
git add src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/AuditLogEntry.cs \
src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs
git commit -m "feat(transport): add BundleImportId column on AuditLogEntry"
```
@@ -367,16 +367,16 @@ git commit -m "feat(transport): add BundleImportId column on AuditLogEntry"
**Parallelizable with:** none (depends on Task 2)
**Files:**
- Create: `src/ScadaLink.ConfigurationDatabase/Migrations/<timestamp>_AddBundleImportIdToAuditLog.cs` (generated)
- Create: `src/ScadaLink.ConfigurationDatabase/Migrations/<timestamp>_AddBundleImportIdToAuditLog.Designer.cs` (generated)
- Create: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/<timestamp>_AddBundleImportIdToAuditLog.cs` (generated)
- Create: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/<timestamp>_AddBundleImportIdToAuditLog.Designer.cs` (generated)
**Step 1: Generate**
Run:
```bash
dotnet ef migrations add AddBundleImportIdToAuditLog \
-p src/ScadaLink.ConfigurationDatabase \
-s src/ScadaLink.Host
-p src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase \
-s src/ZB.MOM.WW.ScadaBridge.Host
```
Expected: two files generated under `Migrations/` with naming pattern `yyyyMMddHHmmss_AddBundleImportIdToAuditLog.cs`.
@@ -390,13 +390,13 @@ If the generated migration includes anything else (drift from other entity chang
**Step 3: Build**
Run: `dotnet build src/ScadaLink.ConfigurationDatabase/ScadaLink.ConfigurationDatabase.csproj`
Run: `dotnet build src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.csproj`
Expected: build succeeds.
**Step 4: Commit**
```bash
git add src/ScadaLink.ConfigurationDatabase/Migrations/
git add src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/
git commit -m "feat(transport): EF migration AddBundleImportIdToAuditLog"
```
@@ -409,16 +409,16 @@ git commit -m "feat(transport): EF migration AddBundleImportIdToAuditLog"
**Parallelizable with:** Task 0, Task 1, Task 2, Task 19
**Files:**
- Create: `src/ScadaLink.ConfigurationDatabase/Services/AuditCorrelationContext.cs`
- Modify: `src/ScadaLink.ConfigurationDatabase/ServiceCollectionExtensions.cs` (add scoped registration; if no file, create alongside existing convention used by audit service)
- Create: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/AuditCorrelationContext.cs`
- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs` (add scoped registration; if no file, create alongside existing convention used by audit service)
**Step 1: Write the implementation**
`AuditCorrelationContext.cs`:
```csharp
using ScadaLink.Commons.Interfaces.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
namespace ScadaLink.ConfigurationDatabase.Services;
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Services;
/// <summary>
/// Per-scope mutable holder for the active bundle import id. AuditService reads it
@@ -434,22 +434,22 @@ public sealed class AuditCorrelationContext : IAuditCorrelationContext
**Step 2: Register**
In `src/ScadaLink.ConfigurationDatabase/ServiceCollectionExtensions.cs` (the `AddConfigurationDatabase()` method), add:
In `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs` (the `AddConfigurationDatabase()` method), add:
```csharp
services.AddScoped<IAuditCorrelationContext, AuditCorrelationContext>();
```
If a `ServiceCollectionExtensions.cs` doesn't exist in `ScadaLink.ConfigurationDatabase`, look at where `IAuditService` is currently registered (search the repo for `AddScoped<IAuditService`) and add the registration adjacent to it.
If a `ServiceCollectionExtensions.cs` doesn't exist in `ZB.MOM.WW.ScadaBridge.ConfigurationDatabase`, look at where `IAuditService` is currently registered (search the repo for `AddScoped<IAuditService`) and add the registration adjacent to it.
**Step 3: Build**
Run: `dotnet build src/ScadaLink.ConfigurationDatabase/ScadaLink.ConfigurationDatabase.csproj`
Run: `dotnet build src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.csproj`
Expected: build succeeds.
**Step 4: Commit**
```bash
git add src/ScadaLink.ConfigurationDatabase/Services/AuditCorrelationContext.cs \
src/ScadaLink.ConfigurationDatabase/ServiceCollectionExtensions.cs
git add src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/AuditCorrelationContext.cs \
src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs
git commit -m "feat(transport): add IAuditCorrelationContext scoped service"
```
@@ -462,8 +462,8 @@ git commit -m "feat(transport): add IAuditCorrelationContext scoped service"
**Parallelizable with:** none (depends on Tasks 2 + 4)
**Files:**
- Modify: `src/ScadaLink.ConfigurationDatabase/Services/AuditService.cs`
- Modify: `tests/ScadaLink.Commons.Tests/AuditServiceTests.cs` (if exists) OR create `tests/ScadaLink.ConfigurationDatabase.Tests/AuditServiceTests.cs`
- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/AuditService.cs`
- Modify: `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/AuditServiceTests.cs` (if exists) OR create `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/AuditServiceTests.cs`
**Step 1: Write the failing test first**
@@ -471,7 +471,7 @@ Add test that sets `IAuditCorrelationContext.BundleImportId`, calls `IAuditServi
**Step 2: Run to verify it fails**
Run: `dotnet test tests/ScadaLink.ConfigurationDatabase.Tests/`
Run: `dotnet test tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/`
Expected: FAIL (correlation not yet wired through).
**Step 3: Implement**
@@ -482,36 +482,36 @@ In `AuditService.cs`:
**Step 4: Run tests**
Run: `dotnet test tests/ScadaLink.ConfigurationDatabase.Tests/`
Run: `dotnet test tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/`
Expected: PASS.
**Step 5: Commit**
```bash
git add src/ScadaLink.ConfigurationDatabase/Services/AuditService.cs \
tests/ScadaLink.ConfigurationDatabase.Tests/
git add src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Services/AuditService.cs \
tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/
git commit -m "feat(transport): AuditService stamps BundleImportId from correlation context"
```
---
## Task 6: `ScadaLink.Transport` project skeleton
## Task 6: `ZB.MOM.WW.ScadaBridge.Transport` project skeleton
**Classification:** small
**Estimated implement time:** ~3 min
**Parallelizable with:** none (subsequent tasks depend on the project existing)
**Files:**
- Create: `src/ScadaLink.Transport/ScadaLink.Transport.csproj`
- Create: `src/ScadaLink.Transport/ServiceCollectionExtensions.cs`
- Modify: `ScadaLink.slnx` (add `<Project Path="src/ScadaLink.Transport/ScadaLink.Transport.csproj" />` in alphabetical position)
- Create: `tests/ScadaLink.Transport.Tests/ScadaLink.Transport.Tests.csproj`
- Create: `tests/ScadaLink.Transport.IntegrationTests/ScadaLink.Transport.IntegrationTests.csproj`
- Modify: `ScadaLink.slnx` (add the two test projects)
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/ZB.MOM.WW.ScadaBridge.Transport.csproj`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/ServiceCollectionExtensions.cs`
- Modify: `ZB.MOM.WW.ScadaBridge.slnx` (add `<Project Path="src/ZB.MOM.WW.ScadaBridge.Transport/ZB.MOM.WW.ScadaBridge.Transport.csproj" />` in alphabetical position)
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/ZB.MOM.WW.ScadaBridge.Transport.Tests.csproj`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests.csproj`
- Modify: `ZB.MOM.WW.ScadaBridge.slnx` (add the two test projects)
**Step 1: Write the csproj**
`ScadaLink.Transport.csproj`:
`ZB.MOM.WW.ScadaBridge.Transport.csproj`:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
@@ -521,13 +521,13 @@ git commit -m "feat(transport): AuditService stamps BundleImportId from correlat
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../ScadaLink.Commons/ScadaLink.Commons.csproj" />
<ProjectReference Include="../ScadaLink.ConfigurationDatabase/ScadaLink.ConfigurationDatabase.csproj" />
<ProjectReference Include="../ScadaLink.TemplateEngine/ScadaLink.TemplateEngine.csproj" />
<ProjectReference Include="../ZB.MOM.WW.ScadaBridge.Commons/ZB.MOM.WW.ScadaBridge.Commons.csproj" />
<ProjectReference Include="../ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.csproj" />
<ProjectReference Include="../ZB.MOM.WW.ScadaBridge.TemplateEngine/ZB.MOM.WW.ScadaBridge.TemplateEngine.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="ScadaLink.Transport.Tests" />
<InternalsVisibleTo Include="ScadaLink.Transport.IntegrationTests" />
<InternalsVisibleTo Include="ZB.MOM.WW.ScadaBridge.Transport.Tests" />
<InternalsVisibleTo Include="ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests" />
</ItemGroup>
</Project>
```
@@ -536,11 +536,11 @@ git commit -m "feat(transport): AuditService stamps BundleImportId from correlat
```csharp
using Microsoft.Extensions.DependencyInjection;
namespace ScadaLink.Transport;
namespace ZB.MOM.WW.ScadaBridge.Transport;
public static class ServiceCollectionExtensions
{
public const string OptionsSection = "ScadaLink:Transport";
public const string OptionsSection = "ScadaBridge:Transport";
public static IServiceCollection AddTransport(this IServiceCollection services)
{
@@ -552,7 +552,7 @@ public static class ServiceCollectionExtensions
}
```
Test csprojs follow the existing test pattern (look at `tests/ScadaLink.NotificationOutbox.Tests/ScadaLink.NotificationOutbox.Tests.csproj` for shape — xUnit + NSubstitute + FluentAssertions + ProjectReference back to ScadaLink.Transport).
Test csprojs follow the existing test pattern (look at `tests/ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests/ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests.csproj` for shape — xUnit + NSubstitute + FluentAssertions + ProjectReference back to ZB.MOM.WW.ScadaBridge.Transport).
**Step 2: Add to slnx**
@@ -560,14 +560,14 @@ Insert in alphabetical order under `/src/` and `/tests/` folder sections.
**Step 3: Build**
Run: `dotnet build ScadaLink.slnx`
Run: `dotnet build ZB.MOM.WW.ScadaBridge.slnx`
Expected: build succeeds (Transport empty + tests empty).
**Step 4: Commit**
```bash
git add src/ScadaLink.Transport/ tests/ScadaLink.Transport.Tests/ tests/ScadaLink.Transport.IntegrationTests/ ScadaLink.slnx
git commit -m "feat(transport): scaffold ScadaLink.Transport project + test projects"
git add src/ZB.MOM.WW.ScadaBridge.Transport/ tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/ tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/ ZB.MOM.WW.ScadaBridge.slnx
git commit -m "feat(transport): scaffold ZB.MOM.WW.ScadaBridge.Transport project + test projects"
```
---
@@ -579,12 +579,12 @@ git commit -m "feat(transport): scaffold ScadaLink.Transport project + test proj
**Parallelizable with:** Task 8 (different file)
**Files:**
- Create: `src/ScadaLink.Transport/TransportOptions.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/TransportOptions.cs`
**Step 1: Write the options class**
```csharp
namespace ScadaLink.Transport;
namespace ZB.MOM.WW.ScadaBridge.Transport;
public sealed class TransportOptions
{
@@ -600,7 +600,7 @@ public sealed class TransportOptions
**Step 2: Commit**
```bash
git add src/ScadaLink.Transport/TransportOptions.cs
git add src/ZB.MOM.WW.ScadaBridge.Transport/TransportOptions.cs
git commit -m "feat(transport): add TransportOptions"
```
@@ -613,8 +613,8 @@ git commit -m "feat(transport): add TransportOptions"
**Parallelizable with:** Task 7, Task 9, Task 10, Task 12, Task 13, Task 19
**Files:**
- Create: `src/ScadaLink.Transport/Encryption/BundleSecretEncryptor.cs`
- Create: `tests/ScadaLink.Transport.Tests/BundleSecretEncryptorTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Encryption/BundleSecretEncryptor.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/BundleSecretEncryptorTests.cs`
**Step 1: Write failing tests first**
@@ -627,16 +627,16 @@ Tests to write (all in `BundleSecretEncryptorTests.cs`):
**Step 2: Run tests to verify failure**
Run: `dotnet test tests/ScadaLink.Transport.Tests/ --filter FullyQualifiedName~BundleSecretEncryptorTests`
Run: `dotnet test tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/ --filter FullyQualifiedName~BundleSecretEncryptorTests`
Expected: FAIL (class not yet defined).
**Step 3: Implement**
```csharp
using System.Security.Cryptography;
using ScadaLink.Commons.Types.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
namespace ScadaLink.Transport.Encryption;
namespace ZB.MOM.WW.ScadaBridge.Transport.Encryption;
public sealed class BundleSecretEncryptor
{
@@ -700,13 +700,13 @@ public sealed class BundleSecretEncryptor
**Step 4: Run tests**
Run: `dotnet test tests/ScadaLink.Transport.Tests/ --filter FullyQualifiedName~BundleSecretEncryptorTests`
Run: `dotnet test tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/ --filter FullyQualifiedName~BundleSecretEncryptorTests`
Expected: PASS.
**Step 5: Commit**
```bash
git add src/ScadaLink.Transport/Encryption/ tests/ScadaLink.Transport.Tests/BundleSecretEncryptorTests.cs
git add src/ZB.MOM.WW.ScadaBridge.Transport/Encryption/ tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/BundleSecretEncryptorTests.cs
git commit -m "feat(transport): AES-256-GCM + PBKDF2 BundleSecretEncryptor"
```
@@ -719,9 +719,9 @@ git commit -m "feat(transport): AES-256-GCM + PBKDF2 BundleSecretEncryptor"
**Parallelizable with:** Task 7, Task 8, Task 10, Task 12, Task 13, Task 19
**Files:**
- Create: `src/ScadaLink.Transport/Serialization/ManifestBuilder.cs`
- Create: `src/ScadaLink.Transport/Serialization/ManifestValidator.cs`
- Create: `tests/ScadaLink.Transport.Tests/ManifestBuilderTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/ManifestBuilder.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/ManifestValidator.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/ManifestBuilderTests.cs`
**Step 1: Write failing tests**
@@ -752,9 +752,9 @@ git commit -m "feat(transport): ManifestBuilder + ManifestValidator with schema-
**Parallelizable with:** Task 7, Task 8, Task 9, Task 12, Task 13, Task 19
**Files:**
- Create: `src/ScadaLink.Transport/Serialization/EntityDtos.cs` (all bundle DTOs: `TemplateDto`, `TemplateFolderDto`, `SharedScriptDto`, `ExternalSystemDto`, `DatabaseConnectionDto`, `NotificationListDto`, `SmtpConfigDto`, `ApiKeyDto`, `ApiMethodDto`, each with a nested `SecretsBlock` where applicable)
- Create: `src/ScadaLink.Transport/Serialization/EntitySerializer.cs`
- Create: `tests/ScadaLink.Transport.Tests/EntitySerializerTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntityDtos.cs` (all bundle DTOs: `TemplateDto`, `TemplateFolderDto`, `SharedScriptDto`, `ExternalSystemDto`, `DatabaseConnectionDto`, `NotificationListDto`, `SmtpConfigDto`, `ApiKeyDto`, `ApiMethodDto`, each with a nested `SecretsBlock` where applicable)
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntitySerializer.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/EntitySerializerTests.cs`
**Step 1: Write failing tests**
@@ -811,8 +811,8 @@ git commit -m "feat(transport): bundle entity DTOs + secret carving in EntitySer
**Parallelizable with:** none (depends on Tasks 8, 9, 10)
**Files:**
- Create: `src/ScadaLink.Transport/Serialization/BundleSerializer.cs`
- Create: `tests/ScadaLink.Transport.Tests/BundleSerializerTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/BundleSerializer.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/BundleSerializerTests.cs`
**Step 1: Write failing tests**
@@ -844,8 +844,8 @@ git commit -m "feat(transport): BundleSerializer ZIP packer/reader"
**Parallelizable with:** Task 7, Task 8, Task 9, Task 10, Task 13, Task 19
**Files:**
- Create: `src/ScadaLink.Transport/Export/DependencyResolver.cs`
- Create: `tests/ScadaLink.Transport.Tests/DependencyResolverTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Export/DependencyResolver.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/DependencyResolverTests.cs`
**Step 1: Write failing tests**
@@ -886,8 +886,8 @@ git commit -m "feat(transport): DependencyResolver with topological closure"
**Parallelizable with:** Task 7, Task 8, Task 9, Task 10, Task 12, Task 19
**Files:**
- Create: `src/ScadaLink.Transport/Import/BundleSessionStore.cs`
- Create: `tests/ScadaLink.Transport.Tests/BundleSessionStoreTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleSessionStore.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/BundleSessionStoreTests.cs`
**Step 1: Write failing tests**
@@ -916,9 +916,9 @@ git commit -m "feat(transport): in-memory BundleSessionStore with TTL + lockout"
**Parallelizable with:** none (depends on Tasks 812)
**Files:**
- Create: `src/ScadaLink.Transport/Export/BundleExporter.cs`
- Create: `tests/ScadaLink.Transport.IntegrationTests/BundleExporterTests.cs` (integration — needs DB)
- Modify: `src/ScadaLink.Transport/ServiceCollectionExtensions.cs` (register `IBundleExporter -> BundleExporter`)
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Export/BundleExporter.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/BundleExporterTests.cs` (integration — needs DB)
- Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/ServiceCollectionExtensions.cs` (register `IBundleExporter -> BundleExporter`)
**Step 1: Write failing integration test**
@@ -970,9 +970,9 @@ git commit -m "feat(transport): BundleExporter with audit logging"
**Parallelizable with:** none (depends on Tasks 11, 13)
**Files:**
- Create: `src/ScadaLink.Transport/Import/BundleImporter.cs`
- Create: `tests/ScadaLink.Transport.Tests/BundleImporterLoadTests.cs`
- Modify: `src/ScadaLink.Transport/ServiceCollectionExtensions.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/BundleImporterLoadTests.cs`
- Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/ServiceCollectionExtensions.cs`
**Step 1: Write failing tests**
@@ -1014,9 +1014,9 @@ git commit -m "feat(transport): BundleImporter.LoadAsync with manifest validatio
**Parallelizable with:** none (depends on Task 15)
**Files:**
- Modify: `src/ScadaLink.Transport/Import/BundleImporter.cs`
- Create: `src/ScadaLink.Transport/Import/ArtifactDiff.cs`
- Create: `tests/ScadaLink.Transport.IntegrationTests/BundleImporterPreviewTests.cs`
- Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/BundleImporterPreviewTests.cs`
**Step 1: Write failing integration tests**
@@ -1046,8 +1046,8 @@ git commit -m "feat(transport): BundleImporter.PreviewAsync diff engine"
**Parallelizable with:** none (depends on Task 16)
**Files:**
- Modify: `src/ScadaLink.Transport/Import/BundleImporter.cs`
- Create: `tests/ScadaLink.Transport.IntegrationTests/BundleImporterApplyTests.cs`
- Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/BundleImporterApplyTests.cs`
**Step 1: Write failing integration tests**
@@ -1132,9 +1132,9 @@ git commit -m "feat(transport): BundleImporter.ApplyAsync transactional with aud
**Parallelizable with:** Task 19, Task 27, Task 28
**Files:**
- Modify: `src/ScadaLink.Host/Program.cs` (add `builder.Services.AddTransport();` in the "Central-only components" section, alongside `AddNotificationOutbox()`)
- Modify: `src/ScadaLink.Host/appsettings.json` (add `ScadaLink:Transport` section with defaults that match `TransportOptions`)
- Modify: `src/ScadaLink.Host/ScadaLink.Host.csproj` (add `ProjectReference` to ScadaLink.Transport)
- Modify: `src/ZB.MOM.WW.ScadaBridge.Host/Program.cs` (add `builder.Services.AddTransport();` in the "Central-only components" section, alongside `AddNotificationOutbox()`)
- Modify: `src/ZB.MOM.WW.ScadaBridge.Host/appsettings.json` (add `ScadaBridge:Transport` section with defaults that match `TransportOptions`)
- Modify: `src/ZB.MOM.WW.ScadaBridge.Host/ZB.MOM.WW.ScadaBridge.Host.csproj` (add `ProjectReference` to ZB.MOM.WW.ScadaBridge.Transport)
**Step 1: Modify Program.cs**
@@ -1143,7 +1143,7 @@ Find the existing `if (centralRolesActive) { ... builder.Services.AddNotificatio
**Step 2: appsettings.json**
```json
"ScadaLink": {
"ScadaBridge": {
"Transport": {
"BundleSessionTtlMinutes": 30,
"MaxBundleSizeMb": 100,
@@ -1157,13 +1157,13 @@ Find the existing `if (centralRolesActive) { ... builder.Services.AddNotificatio
**Step 3: Build**
Run: `dotnet build ScadaLink.slnx`
Run: `dotnet build ZB.MOM.WW.ScadaBridge.slnx`
Expected: build succeeds.
**Step 4: Commit**
```bash
git add src/ScadaLink.Host/
git add src/ZB.MOM.WW.ScadaBridge.Host/
git commit -m "feat(transport): register AddTransport() on central nodes"
```
@@ -1176,8 +1176,8 @@ git commit -m "feat(transport): register AddTransport() on central nodes"
**Parallelizable with:** Task 0, Task 1, Task 2, Task 4, Task 7, Task 8, Task 9, Task 10, Task 12, Task 13
**Files:**
- Modify: `src/ScadaLink.CentralUI/Components/Shared/TreeView.razor`
- Create: `tests/ScadaLink.CentralUI.Tests/TreeViewMultiSelectTests.cs`
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TreeView.razor`
- Create: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TreeViewMultiSelectTests.cs`
**Step 1: Extend the component API**
@@ -1201,7 +1201,7 @@ Toggle semantics:
**Step 2: Write failing tests**
Use bUnit (already in `tests/ScadaLink.CentralUI.Tests/`):
Use bUnit (already in `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/`):
1. `Checkbox_mode_renders_checkbox_per_node`
2. `Clicking_folder_selects_all_descendants`
3. `Clicking_leaf_makes_parent_indeterminate_when_sibling_unchecked`
@@ -1209,14 +1209,14 @@ Use bUnit (already in `tests/ScadaLink.CentralUI.Tests/`):
**Step 4: Run tests**
Run: `dotnet test tests/ScadaLink.CentralUI.Tests/ --filter FullyQualifiedName~TreeViewMultiSelect`
Run: `dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ --filter FullyQualifiedName~TreeViewMultiSelect`
Expected: PASS.
**Step 5: Commit**
```bash
git add src/ScadaLink.CentralUI/Components/Shared/TreeView.razor \
tests/ScadaLink.CentralUI.Tests/TreeViewMultiSelectTests.cs
git add src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TreeView.razor \
tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TreeViewMultiSelectTests.cs
git commit -m "feat(centralui): TreeView checkbox-selection mode with tri-state"
```
@@ -1229,8 +1229,8 @@ git commit -m "feat(centralui): TreeView checkbox-selection mode with tri-state"
**Parallelizable with:** none (depends on Task 19)
**Files:**
- Create: `src/ScadaLink.CentralUI/Components/Shared/TemplateFolderTree.razor`
- Modify: `src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor` (use the new component in `Single` mode)
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TemplateFolderTree.razor`
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/Templates.razor` (use the new component in `Single` mode)
**Step 1: Build the wrapper**
@@ -1251,15 +1251,15 @@ Replace the existing inline tree usage with `<TemplateFolderTree Folders="..." T
**Step 3: Manual smoke (no new tests needed — existing Templates.razor tests cover this)**
Run: `dotnet build src/ScadaLink.CentralUI/ScadaLink.CentralUI.csproj`
Then: `dotnet test tests/ScadaLink.CentralUI.Tests/`
Run: `dotnet build src/ZB.MOM.WW.ScadaBridge.CentralUI/ZB.MOM.WW.ScadaBridge.CentralUI.csproj`
Then: `dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/`
Expected: all existing Templates page tests still pass.
**Step 4: Commit**
```bash
git add src/ScadaLink.CentralUI/Components/Shared/TemplateFolderTree.razor \
src/ScadaLink.CentralUI/Components/Pages/Design/Templates.razor
git add src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/TemplateFolderTree.razor \
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/Templates.razor
git commit -m "refactor(centralui): extract TemplateFolderTree as shared component"
```
@@ -1272,17 +1272,17 @@ git commit -m "refactor(centralui): extract TemplateFolderTree as shared compone
**Parallelizable with:** Task 22 (separate page; only NavMenu touches both)
**Files:**
- Create: `src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor`
- Create: `src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor.cs` (code-behind for the wizard state machine)
- Create: `tests/ScadaLink.CentralUI.Tests/TransportExportPageTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor`
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor.cs` (code-behind for the wizard state machine)
- Create: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TransportExportPageTests.cs`
**Step 1: Page skeleton**
```razor
@page "/design/transport/export"
@using ScadaLink.Security
@using ScadaLink.Commons.Types.Transport
@using ScadaLink.Commons.Interfaces.Transport
@using ZB.MOM.WW.ScadaBridge.Security
@using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDesign)]
@inject IBundleExporter BundleExporter
@inject ITemplateEngineRepository TemplateRepo
@@ -1302,7 +1302,7 @@ Step 2 calls `DependencyResolver` server-side and shows the expanded selection (
Step 3 collects passphrase + confirm (or explicit "Export without encryption" path with warning banner).
Step 4 calls `IBundleExporter.ExportAsync`, streams the result to the browser via `IJSRuntime.InvokeVoidAsync("scadalinkTransport.downloadBundle", filename, bytes)`. Display SHA-256 + size.
Step 4 calls `IBundleExporter.ExportAsync`, streams the result to the browser via `IJSRuntime.InvokeVoidAsync("scadabridgeTransport.downloadBundle", filename, bytes)`. Display SHA-256 + size.
**Step 2: Page tests with bUnit**
@@ -1313,15 +1313,15 @@ Step 4 calls `IBundleExporter.ExportAsync`, streams the result to the browser vi
**Step 3: Run tests**
Run: `dotnet test tests/ScadaLink.CentralUI.Tests/ --filter FullyQualifiedName~TransportExportPage`
Run: `dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ --filter FullyQualifiedName~TransportExportPage`
Expected: PASS.
**Step 4: Commit**
```bash
git add src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor \
src/ScadaLink.CentralUI/Components/Pages/Design/TransportExport.razor.cs \
tests/ScadaLink.CentralUI.Tests/TransportExportPageTests.cs
git add src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor \
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportExport.razor.cs \
tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TransportExportPageTests.cs
git commit -m "feat(centralui): TransportExport wizard under Design nav group"
```
@@ -1334,17 +1334,17 @@ git commit -m "feat(centralui): TransportExport wizard under Design nav group"
**Parallelizable with:** Task 21
**Files:**
- Create: `src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor`
- Create: `src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs`
- Create: `tests/ScadaLink.CentralUI.Tests/TransportImportPageTests.cs`
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor`
- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TransportImportPageTests.cs`
**Step 1: Page skeleton**
```razor
@page "/design/transport/import"
@using ScadaLink.Security
@using ScadaLink.Commons.Types.Transport
@using ScadaLink.Commons.Interfaces.Transport
@using ZB.MOM.WW.ScadaBridge.Security
@using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport
@using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport
@attribute [Authorize(Policy = AuthorizationPolicies.RequireAdmin)]
@inject IBundleImporter BundleImporter
@inject NavigationManager Nav
@@ -1370,9 +1370,9 @@ Five-step wizard:
**Step 3: Run + commit**
```bash
git add src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor \
src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs \
tests/ScadaLink.CentralUI.Tests/TransportImportPageTests.cs
git add src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor \
src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TransportImport.razor.cs \
tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TransportImportPageTests.cs
git commit -m "feat(centralui): TransportImport wizard under Design nav group"
```
@@ -1385,7 +1385,7 @@ git commit -m "feat(centralui): TransportImport wizard under Design nav group"
**Parallelizable with:** none (depends on Tasks 21, 22)
**Files:**
- Modify: `src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor`
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor`
**Step 1: Add nav entries**
@@ -1407,13 +1407,13 @@ Inside the existing Design `<NavSection>`, after the Templates entry:
**Step 2: Build + smoke-test**
Run: `dotnet build src/ScadaLink.CentralUI/ScadaLink.CentralUI.csproj`
Run: `dotnet build src/ZB.MOM.WW.ScadaBridge.CentralUI/ZB.MOM.WW.ScadaBridge.CentralUI.csproj`
Expected: build succeeds.
**Step 3: Commit**
```bash
git add src/ScadaLink.CentralUI/Components/Layout/NavMenu.razor
git add src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Layout/NavMenu.razor
git commit -m "feat(centralui): add Export/Import Bundle nav entries"
```
@@ -1426,8 +1426,8 @@ git commit -m "feat(centralui): add Export/Import Bundle nav entries"
**Parallelizable with:** Task 22, Task 23
**Files:**
- Modify: `src/ScadaLink.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor` (locate by `git grep -l ConfigurationAuditLog src/ScadaLink.CentralUI/`)
- Modify: `src/ScadaLink.ConfigurationDatabase/Repositories/` (the repository that drives the audit page — extend the query with an optional `Guid? bundleImportId` filter)
- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/ConfigurationAuditLog.razor` (locate by `git grep -l ConfigurationAuditLog src/ZB.MOM.WW.ScadaBridge.CentralUI/`)
- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/` (the repository that drives the audit page — extend the query with an optional `Guid? bundleImportId` filter)
**Step 1: Extend the repository query**
@@ -1461,7 +1461,7 @@ git commit -m "feat(centralui): Bundle Import filter on ConfigurationAuditLog pa
**Parallelizable with:** Task 26, Task 27, Task 28
**Files:**
- Create: `tests/ScadaLink.Transport.IntegrationTests/RoundTripTests.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/RoundTripTests.cs`
**Step 1: Write the test**
@@ -1484,7 +1484,7 @@ public async Task Export_then_wipe_then_import_restores_state()
**Step 2: Run + commit**
```bash
git add tests/ScadaLink.Transport.IntegrationTests/RoundTripTests.cs
git add tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/RoundTripTests.cs
git commit -m "test(transport): integration round-trip export → wipe → import"
```
@@ -1497,8 +1497,8 @@ git commit -m "test(transport): integration round-trip export → wipe → impor
**Parallelizable with:** Task 25, Task 27, Task 28
**Files:**
- Create: `tests/ScadaLink.Transport.IntegrationTests/ConflictResolutionTests.cs`
- Create: `tests/ScadaLink.Transport.IntegrationTests/ValidationFailureTests.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/ConflictResolutionTests.cs`
- Create: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/ValidationFailureTests.cs`
**Step 1: Write the tests**
@@ -1513,7 +1513,7 @@ git commit -m "test(transport): integration round-trip export → wipe → impor
**Step 3: Commit**
```bash
git add tests/ScadaLink.Transport.IntegrationTests/
git add tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/
git commit -m "test(transport): integration conflict resolution + rollback"
```
@@ -1644,11 +1644,11 @@ git commit -m "docs(transport): manual cluster verification checklist"
After Task 29:
```bash
dotnet build ScadaLink.slnx
dotnet test tests/ScadaLink.Transport.Tests/
dotnet test tests/ScadaLink.Transport.IntegrationTests/
dotnet test tests/ScadaLink.CentralUI.Tests/
dotnet test tests/ScadaLink.ConfigurationDatabase.Tests/
dotnet build ZB.MOM.WW.ScadaBridge.slnx
dotnet test tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/
dotnet test tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/
dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/
dotnet test tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/
```
All must pass. Then proceed to Task 29 manual checklist against the docker cluster.
@@ -1658,7 +1658,7 @@ All must pass. Then proceed to Task 29 manual checklist against the docker clust
## Out-of-Scope Reminders (do NOT do)
- Do NOT add a feature flag.
- Do NOT touch site-side projects (`ScadaLink.SiteRuntime` etc.).
- Do NOT touch site-side projects (`ZB.MOM.WW.ScadaBridge.SiteRuntime` etc.).
- Do NOT add a stale-mark column on `Instance` — the existing `DeploymentService.CompareAsync` already detects this.
- Do NOT build CLI commands — those are deferred per design doc §13.
- Do NOT add bundle signing — content hash in manifest is sufficient for v1.