# Galaxy Standard Driver — Phase A Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. **Goal:** Make `GalaxyMxGateway` a valid **Equipment-kind** driver — retire the `SystemPlatform` namespace split, the SystemPlatform mirror, and the alias-tag/relay machinery — so Galaxy points are authored as ordinary equipment Tags through the standard `TagModal`. **Architecture:** Phase A is **mostly deletion + relaxing validation**, not new runtime wiring. Galaxy live values already work on equipment tags (alias tags prove it via direct driver subscription by `FullName`). We remove the `NamespaceKind.SystemPlatform` enum value and every reference (validator, composer/artifact mirror, authorization, registry, namespace-create UI), delete the alias/relay AdminUI machinery, and add one EF migration to drop the per-kind unique constraint. Native Galaxy alarms (Phase B) and server-side historian (Phase C) are **out of scope** and get their own plans. **Tech Stack:** .NET 10, EF Core (SQL Server, string-converted enum column), Akka.NET, Blazor Server (no bUnit), xUnit + Shouldly. Design doc: `docs/plans/2026-06-12-galaxy-standard-driver-design.md` (master `91cb9076`). **Branch:** `feat/galaxy-standard-driver-phase-a` off master `91cb9076`. --- ## Hard rules (every task) - Stage by path; **never** `git add .`. Never stage `sql_login.txt`, `src/Server/ZB.MOM.WW.OtOpcUa.Host/pki/`, or `pending.md`. - Never echo the gateway API key or the historian `SharedSecret` into a tracked file. - No force-push, no `--no-verify`. - Commit per task, by explicit path. ## Dependency spine (read before executing) - **Composition contract:** Task 4 removes `GalaxyTags` from `Phase7CompositionResult` + the `GalaxyTagPlan` record. Tasks 5, 6, 7 depend on that shape change. Task 4 → 5 → 6, and 4 → 7. - **Enum capstone:** Task 9 removes the `NamespaceKind.SystemPlatform` / `NamespaceKindCompatibility.SystemPlatform` enum values. It is the **capstone** — every reference must already be gone, so Task 9 is `blockedBy` Tasks 3, 4, 5, 8. Build must be green after Task 9. - **Migration:** Task 10 (EF migration + DbContext index change) is `blockedBy` Task 9 (the model must be settled). - **AdminUI spine:** `UnsTreeService.cs` / `IUnsTreeService.cs` / `EquipmentChildRows.cs` change in lockstep (Task 12). `EquipmentPage.razor` (Task 13) and `TagModal.razor` (Task 14) depend on Task 12 and edit disjoint files (parallelizable with each other). ## Task order 0. Branch 1. DriverTypeRegistry: GalaxyMxGateway → Equipment 2. DraftValidator: drop Galaxy/SystemPlatform branches + rename alias validation 3. Phase7Composer: remove Galaxy mirror producer + exception clause 4. Phase7CompositionResult + GalaxyTagPlan: remove `GalaxyTags` 5. DeploymentArtifact: remove Galaxy mirror decode + isGalaxyAlias exception 6. Phase7Applier + OpcUaPublishActor: delete `MaterialiseGalaxyTags` + call site 7. Authorization: remove `ScopeKind.SystemPlatform` + `WalkSystemPlatform` 8. Composer↔Artifact byte-parity test (Galaxy equipment tag) 9. **Capstone:** remove `SystemPlatform` enum values; build green 10. EF migration: drop per-kind unique constraint + delete orphaned rows 11. Delete alias-only AdminUI files 12. UnsTreeService/IUnsTreeService/EquipmentChildRows: strip alias/relay 13. EquipmentPage.razor: strip alias UI 14. TagModal.razor: wire Galaxy picker 15. NamespaceEdit.razor: hide SystemPlatform option 16. Docs: Uns.md + CLAUDE.md 17. Verify: build + test + user-driven live `/run` --- ### Task 0: Create feature branch **Classification:** trivial **Estimated implement time:** ~1 min **Parallelizable with:** none **Files:** none (git only) **Step 1:** From master at `91cb9076`: ```bash git checkout master git rev-parse HEAD # expect 91cb9076... git checkout -b feat/galaxy-standard-driver-phase-a ``` **Step 2:** Confirm clean tree (ignore untracked `pending.md`, `sql_login.txt`, `pki/`, docs scratch files — never stage them). --- ### Task 1: DriverTypeRegistry — GalaxyMxGateway allowed in Equipment **Classification:** small **Estimated implement time:** ~3 min **Parallelizable with:** Task 11 **Files:** - Modify: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/GalaxyDriverFactoryExtensions.cs` (the `DriverTypeMetadata(...)` registration — its `AllowedNamespaceKinds` arg) - Test: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/` (registration metadata test, if one exists; otherwise assert in an existing factory test) **Context:** `DriverTypeMetadata.AllowedNamespaceKinds` is a `NamespaceKindCompatibility` flags value. Galaxy currently registers `NamespaceKindCompatibility.SystemPlatform`. Change it to `NamespaceKindCompatibility.Equipment`. **Step 1:** Grep for the registration to find the exact line: ```bash grep -n "NamespaceKindCompatibility\|DriverTypeMetadata(" src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/GalaxyDriverFactoryExtensions.cs ``` **Step 2:** Change the `AllowedNamespaceKinds` argument from `NamespaceKindCompatibility.SystemPlatform` to `NamespaceKindCompatibility.Equipment`. **Step 3:** Build the Galaxy driver project: ```bash dotnet build src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy ``` Expected: 0 errors. **Step 4:** Commit: ```bash git add src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy/GalaxyDriverFactoryExtensions.cs git commit -m "feat(galaxy): register GalaxyMxGateway as an Equipment-kind driver" ``` --- ### Task 2: DraftValidator — drop Galaxy/SystemPlatform branches; rename alias validation **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 11 **Files:** - Modify: `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs:34,36,41-55,225-245` - Test: `tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/Validation/DraftValidatorTests.cs` (or the existing validator test file — grep for `DriverNamespaceKindMismatch` / `AliasTagMissingReference`) **Context:** `ValidateDriverNamespaceCompatibility` (225-245) currently rejects `GalaxyMxGateway` under Equipment and rejects non-Galaxy under SystemPlatform. After de-split, Galaxy under Equipment must be **valid**. `ValidateAliasTagFullName` (41-55) requires `TagConfig.FullName` on a Galaxy-bound equipment tag — **keep this rule** (a Galaxy equipment tag still needs a reference) but rename it from "alias" to "Galaxy tag". **Step 1 (test first):** Add/extend a test: a draft with a `GalaxyMxGateway` driver in an **Equipment** namespace produces **no** `DriverNamespaceKindMismatch` error; a Galaxy equipment tag with empty `TagConfig` still produces the missing-reference error (now `GalaxyTagMissingReference`). Run it — expect FAIL (current code rejects Galaxy under Equipment). **Step 2:** In `ValidateDriverNamespaceCompatibility`, delete the switch's Galaxy/SystemPlatform branches. With `SystemPlatform` gone the method has no remaining rule — **delete the method and its call at line 34**. (If a reviewer prefers a stub for future rules, leaving a `=> true` no-op is acceptable, but YAGNI says delete.) **Step 3:** Rename `ValidateAliasTagFullName` → `ValidateGalaxyTagFullName`; change the error code `AliasTagMissingReference` → `GalaxyTagMissingReference` and the message wording from "Alias tag" to "Galaxy tag". Keep `ExtractTagConfigFullName` (also used by `{{equip}}` validation elsewhere). **Step 4:** Run the validator tests — expect PASS. ```bash dotnet test tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests --filter "FullyQualifiedName~DraftValidator" ``` **Step 5:** Commit: ```bash git add src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests/Validation/DraftValidatorTests.cs git commit -m "feat(validation): allow GalaxyMxGateway under Equipment; rename Galaxy-tag FullName check" ``` --- ### Task 3: Phase7Composer — remove the Galaxy mirror producer + exception clause **Classification:** high-risk **Estimated implement time:** ~5 min **Parallelizable with:** none (composition contract; gates Tasks 4-8) **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Phase7Composer.cs:338-360,365-374` - Test: `tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests/` (existing composer tests — grep `GalaxyTag`, `MaterialiseGalaxy`, `equipmentTags`) **Context:** Lines 338-360 build `galaxyTags` (the SystemPlatform mirror, `t.EquipmentId is null` + `ns.Kind == SystemPlatform`). Lines 365-374 build `equipmentTags` with the Galaxy exception clause `(ns.Kind == NamespaceKind.Equipment || di.DriverType == "GalaxyMxGateway")`. After de-split a Galaxy driver lives in an Equipment namespace, so the exception is dead. **Step 1:** Delete the `galaxyTags` producer (338-360) and stop returning it in the `Phase7CompositionResult` it constructs (the `GalaxyTags:` argument — coordinated with Task 4; if Task 4 hasn't run yet, temporarily pass `[]`). **Step 2:** In the `equipmentTags` predicate, remove `|| di.DriverType == "GalaxyMxGateway"` so it reads `&& ns.Kind == NamespaceKind.Equipment`. Update the now-stale comment. **Step 3:** Update composer tests: a Galaxy equipment tag (EquipmentId set, GalaxyMxGateway, Equipment-kind ns) appears in `EquipmentTags`; assert no `GalaxyTags` are produced. Run: ```bash dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests --filter "FullyQualifiedName~Phase7Composer" ``` **Step 4:** Commit (composer + its tests). --- ### Task 4: Phase7CompositionResult — remove `GalaxyTags` + `GalaxyTagPlan` **Classification:** standard **Estimated implement time:** ~3 min **Parallelizable with:** none **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Phase7Composer.cs` (the `Phase7CompositionResult` record `GalaxyTags` member + the `GalaxyTagPlan` record def, ~lines 21, 91-97) **Context:** Once no producer emits Galaxy mirror plans, remove the contract. **Step 1:** Remove the `GalaxyTags` property from `Phase7CompositionResult` and delete the `GalaxyTagPlan` record. Fix the composer's result construction (drop the `GalaxyTags:` arg). **Step 2:** Build the OpcUaServer project; fix any remaining references in this project only (Task 5/6 handle the others). Expect references in DeploymentArtifact + Phase7Applier to break — those are Tasks 5 and 6. ```bash dotnet build src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer ``` **Step 3:** Commit. **blockedBy:** Task 3. --- ### Task 5: DeploymentArtifact — remove Galaxy mirror decode + isGalaxyAlias exception **Classification:** high-risk **Estimated implement time:** ~5 min **Parallelizable with:** none (byte-parity partner of composer) **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DeploymentArtifact.cs:366-437,375-391,447-535,712-727` **Context:** `BuildGalaxyTagPlans` (366-437) mirrors the composer's mirror producer; the `systemPlatformNamespaces` decode (375-391) reads `Kind==1`/"SystemPlatform"; `BuildEquipmentTagPlans` (447-535) carries the `driverToType` map + `isGalaxyAlias` exception (513-514). All retire. **Must stay byte-parity with the composer** (Task 3): equipment-tag plans must be identical for the same input. **Step 1:** Delete `BuildGalaxyTagPlans` and its call site; remove the `GalaxyTags` from the artifact's decoded composition (matching Task 4's contract). **Step 2:** Delete the `systemPlatformNamespaces` decode block (375-391) and any use of it. **Step 3:** In `BuildEquipmentTagPlans`, remove the `driverToType` map and the `isGalaxyAlias` exception; the equipment-tag gate becomes `if (!equipmentNamespaces.Contains(nsId)) continue;`. Keep `ExtractTagFullName` (712-727) — Galaxy equipment tags still carry `FullName`. **Step 4:** Build Runtime + OpcUaServer: ```bash dotnet build src/Server/ZB.MOM.WW.OtOpcUa.Runtime ``` Expect 0 errors. Run existing DeploymentArtifact tests. **Step 5:** Commit. **blockedBy:** Task 4. --- ### Task 6: Phase7Applier + OpcUaPublishActor — delete MaterialiseGalaxyTags + call site **Classification:** standard **Estimated implement time:** ~3 min **Parallelizable with:** Task 7 **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/Phase7Applier.cs:152-179` (delete `MaterialiseGalaxyTags`) - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.Runtime/OpcUa/OpcUaPublishActor.cs:~244-248` (delete the `_applier.MaterialiseGalaxyTags(composition);` call + its comment) **Context:** `MaterialiseEquipmentTags` (202-239) stays and already materializes Galaxy equipment tags. Only the mirror pass retires. **Step 1:** Delete `MaterialiseGalaxyTags` from `Phase7Applier`. **Step 2:** Delete the call in `OpcUaPublishActor` (the `GalaxyTags` comment + the `MaterialiseGalaxyTags(composition)` line). **Step 3:** Build + run applier/publish tests (grep tests for `MaterialiseGalaxy` and delete/adjust any). ```bash dotnet build src/Server/ZB.MOM.WW.OtOpcUa.Runtime ``` **Step 4:** Commit. **blockedBy:** Task 4. --- ### Task 7: Authorization — remove ScopeKind.SystemPlatform + WalkSystemPlatform **Classification:** high-risk **Estimated implement time:** ~4 min **Parallelizable with:** Task 6 **Files:** - Modify: `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/NodeScope.cs:~57` (remove `SystemPlatform` from the `ScopeKind` enum) - Modify: `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieBuilder.cs` (the `WalkSystemPlatform` call ~line 58 + method def ~82-90) - Test: `tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests/Authorization/` (grep `SystemPlatform`) **Context:** With no SystemPlatform namespaces possible, the SystemPlatform ACL walk is dead. Galaxy permissions now flow through the standard Equipment walk (Galaxy tags are equipment tags). This is authz — review carefully. **Step 1:** Remove the `WalkSystemPlatform` call and method, and the `ScopeKind.SystemPlatform` enum value. Confirm the Equipment walk covers Galaxy equipment tags (it keys on EquipmentId/path, driver-agnostic). **Step 2:** Update/remove any authz test asserting a SystemPlatform scope. Add an assertion (if a fixture exists) that a Galaxy equipment tag resolves under the Equipment permission trie. **Step 3:** Build Core + run authz tests: ```bash dotnet test tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests --filter "FullyQualifiedName~Authoriz" ``` **Step 4:** Commit. **blockedBy:** Task 4 (compile order; avoids churn while composition contract shifts). --- ### Task 8: Composer ↔ Artifact byte-parity test (Galaxy equipment tag) **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** none **Files:** - Test: `tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/` (extend or replace the existing alias-parity test — grep `DeploymentArtifactAliasParity` / `Phase7ComposerAliasTag`) **Context:** The invariant: for the same draft input, `Phase7Composer.Compose(...).EquipmentTags` and `DeploymentArtifact`'s decoded equipment-tag plans are **identical** when the input includes a Galaxy equipment tag (`EquipmentId` set, `GalaxyMxGateway`, Equipment-kind ns, `TagConfig={"FullName":"DelmiaReceiver_001.DownloadPath"}`). This replaces the old alias-exception parity test with the now-canonical case. **Step 1 (test first):** Author a fixture draft: one Equipment namespace, one GalaxyMxGateway driver bound to it, one equipment tag with the Galaxy `FullName`. Compose via the composer; serialize to artifact; decode; assert the equipment-tag plan lists match exactly (FullName, NodeId scoping, ordering). Run — should pass once Tasks 3+5 are in; if it fails, a parity divergence is a real defect. **Step 2:** Commit. **blockedBy:** Tasks 3, 5. --- ### Task 9: CAPSTONE — remove SystemPlatform enum values; build green **Classification:** high-risk **Estimated implement time:** ~4 min **Parallelizable with:** none **Files:** - Modify: `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/NamespaceKind.cs:13-17` (remove `SystemPlatform`; keep `Equipment`, `Simulated`) - Modify: `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeRegistry.cs:113-114` (remove `NamespaceKindCompatibility.SystemPlatform = 2`) **Context:** Capstone — every reference must already be gone (Tasks 1,2,3,5,7). Removing the value makes any missed reference a compile error, which is the point. **Step 1:** Remove both enum members + their doc comments. **Step 2:** Full-solution build: ```bash dotnet build ZB.MOM.WW.OtOpcUa.slnx ``` Expected: **0 errors.** Any error names a missed reference — fix it in the owning file (do not re-add the enum value). **Step 3:** Full test pass on the touched suites: ```bash dotnet test tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests dotnet test tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests ``` **Step 4:** Commit. **blockedBy:** Tasks 1, 2, 3, 5, 7. (Task 6 too — its call site references nothing SystemPlatform but keep it before the capstone for a clean build.) --- ### Task 10: EF migration — drop per-kind unique constraint + delete orphaned rows **Classification:** high-risk **Estimated implement time:** ~5 min **Parallelizable with:** none **Files:** - Modify: `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/OtOpcUaConfigDbContext.cs:207-208` (change the unique index from `(ClusterId, Kind)` to `(ClusterId)`, rename `UX_Namespace_Cluster_Kind` → `UX_Namespace_Cluster`) - Create: `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/_DropSystemPlatformNamespaceKind.cs` (+ the `.Designer.cs` + updated `OtOpcUaConfigDbContextModelSnapshot.cs`, generated by the tool) **Context:** Clean break. Keep the `Kind` column (still `Equipment`/`Simulated`, string-converted) but stop enforcing one-per-kind; enforce **one namespace per cluster** instead. Delete orphaned `SystemPlatform` rows (and dependents) so the reduced enum never reads an unmapped value. Dev rigs reseed by hand. **Step 1:** Edit `OtOpcUaConfigDbContext.ConfigureNamespace`: change `e.HasIndex(x => new { x.ClusterId, x.Kind }).IsUnique().HasDatabaseName("UX_Namespace_Cluster_Kind")` to `e.HasIndex(x => x.ClusterId).IsUnique().HasDatabaseName("UX_Namespace_Cluster")`. **Step 2:** Author the migration (the design-time factory reads `OTOPCUA_CONFIG_CONNECTION`; `migrations add` does not connect, but set it to be safe): ```bash export OTOPCUA_CONFIG_CONNECTION="Server=10.100.0.35,14330;Database=OtOpcUaConfig;Trusted_Connection=True;TrustServerCertificate=True;" dotnet ef migrations add DropSystemPlatformNamespaceKind --project src/Core/ZB.MOM.WW.OtOpcUa.Configuration ``` **Step 3:** Hand-edit the generated `Up(...)` so the order is safe: ```csharp // 1. delete dependents of SystemPlatform namespaces, then the namespaces themselves, // so the reduced NamespaceKind enum never deserialises a 'SystemPlatform' string. migrationBuilder.Sql(@" DELETE FROM Tag WHERE DriverInstanceId IN (SELECT DriverInstanceId FROM DriverInstance WHERE NamespaceId IN (SELECT NamespaceId FROM Namespace WHERE Kind = 'SystemPlatform')); DELETE FROM DriverInstance WHERE NamespaceId IN (SELECT NamespaceId FROM Namespace WHERE Kind = 'SystemPlatform'); DELETE FROM Namespace WHERE Kind = 'SystemPlatform';"); // 2. swap the unique index. migrationBuilder.DropIndex(name: "UX_Namespace_Cluster_Kind", table: "Namespace"); migrationBuilder.CreateIndex(name: "UX_Namespace_Cluster", table: "Namespace", column: "ClusterId", unique: true); ``` (Verify the actual dependent tables/FKs against the snapshot before finalizing — adjust the DELETE cascade to match real FK names. A reviewer must confirm no dependent table is missed.) **Step 4:** Provide a minimal `Down(...)` (recreate `UX_Namespace_Cluster_Kind`, drop `UX_Namespace_Cluster`; data deletes are not reversible — note that in a comment). Build the Configuration project. **Step 5:** Commit (migration files + DbContext). Do **not** apply the migration here — it applies at deploy/runtime against the central SQL Server. **blockedBy:** Task 9. --- ### Task 11: Delete alias-only AdminUI files **Classification:** small **Estimated implement time:** ~2 min **Parallelizable with:** Tasks 1, 2 **Files (delete):** - `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/AliasTagModal.razor` (+ any `.razor.cs`) - `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RelayConversion.cs` - `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/AliasTagEditDto.cs` - `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/AliasTagInput.cs` - `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Uns/RelayAliasConvert.razor` (route `/uns/convert-relays`) **Context:** These are alias/relay-only. Project won't compile until Task 12 removes the references — that's expected; this task is the file-level delete. **Step 1:** `git rm` each file by path. **Step 2:** Grep for any remaining `RelayAliasConvert` / `convert-relays` nav link (e.g. a `NavMenu`/`NavLink`) and remove it. **Step 3:** Commit (build will be red until Task 12 — note in the commit body). > Note for executor: because Tasks 11+12 leave AdminUI uncompilable between them, > dispatch them as a **bundle** (one reviewer over the combined diff) rather than > gating a build between them. --- ### Task 12: UnsTreeService / IUnsTreeService / EquipmentChildRows — strip alias/relay **Classification:** high-risk **Estimated implement time:** ~5 min **Parallelizable with:** none (AdminUI spine) **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs` (delete: `LoadAliasTagAsync` 207-215, `LoadGalaxyGatewaysForEquipmentAsync` 778-798, `CreateAliasTagAsync` 907-944, `UpdateAliasTagAsync` 947-1001, `ConvertRelayVirtualTagsToAliasesAsync` 1036-1214, `CheckAliasDriverGuardAsync` 1595-1623, `BuildAliasTagConfig` ~1628, `BuildAliasTag` ~1637-1649, the `EquipmentName` record + comparer ~1652-1670; in `LoadTagsForEquipmentAsync` 86-118 remove the IsAlias/Source decoration 90-92 + 113-116). **Keep** `ExtractTagConfigFullName` (used by `{{equip}}` validation). - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/IUnsTreeService.cs` (delete members: `LoadAliasTagAsync` 178-182, `LoadGalaxyGatewaysForEquipmentAsync` 353-358, `CreateAliasTagAsync` 388-399, `UpdateAliasTagAsync` 401-413, `ConvertRelayVirtualTagsToAliasesAsync` 558-567) - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/EquipmentChildRows.cs:10-12` (remove `IsAlias`/`Source` from `EquipmentTagRow`) - Delete: `tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceAliasTagTests.cs`, `tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/UnsTreeServiceRelayConverterTests.cs` **Context:** These three files change in lockstep (interface + impl + row DTO). `LoadTagsForEquipmentAsync` returns `EquipmentTagRow(TagId, Name, DriverInstanceId, DataType, AccessLevel)` after the IsAlias/Source removal. **Step 1:** Delete the listed methods/members and the test files. Trim `LoadTagsForEquipmentAsync` to the base tag load + new row shape. **Step 2:** Build AdminUI **together with Task 13** (this task alone leaves EquipmentPage referencing removed methods). Defer the green build to Task 13. **Step 3:** Commit (service + interface + rows + deleted tests). **blockedBy:** none for code, but **bundle with Task 11** (see note). --- ### Task 13: EquipmentPage.razor — strip alias UI; route Galaxy through standard TagModal **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 14 **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Uns/EquipmentPage.razor` (remove: "Add alias" button ~146, "Convert relay" button ~150, alias row display ~181-186, IsAlias edit/delete routing ~188-195, convert preview panel ~204-289, `AliasTagModal` instance ~301-303, state vars ~426-440, handlers `OpenAddAlias`/`OpenEditAlias`/`OnAliasSavedAsync`/`PreviewConvertRelaysAsync`/`ApplyConvertRelaysAsync` ~526-585, the `LoadGalaxyGatewaysForEquipmentAsync` calls ~482/529, the Source column ~170/184) **Context:** All tags (including Galaxy) use the existing "Add tag" → standard `TagModal` path. The tag table shows no alias badge/Source column; Edit/Delete are uniform for every row. **Step 1:** Remove the alias UI, state, and handlers per the file list. Keep the Details / standard-Tag / VirtualTag / Alarm sections. **Step 2:** Ensure the "Add tag" button opens the standard `TagModal` with the equipment's driver list (which now includes Galaxy drivers — they're Equipment-kind post-de-split). **Step 3:** Build AdminUI (now compilable with Task 12): ```bash dotnet build src/Server/ZB.MOM.WW.OtOpcUa.AdminUI ``` Expected: 0 errors. **Step 4:** Commit. **blockedBy:** Task 12. --- ### Task 14: TagModal.razor — wire the Galaxy address picker **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 13 **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagModal.razor` (picker dispatch: when `SelectedDriverType == "GalaxyMxGateway"` show `GalaxyAddressPickerBody` inside the existing `DriverTagPicker`, writing the chosen ref into `TagConfig` as `{"FullName":"..."}`) - Possibly Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/UnsTreeService.cs` `LoadTagDriversForEquipmentAsync` — verify it returns the Galaxy driver's `DriverConfig` (the picker opens a browse session with the gateway config); if the driver-list tuple lacks `DriverConfig`, extend it. **Context:** The standard `TagModal` already dispatches address pickers by driver type. `GalaxyMxGateway` keeps the raw-JSON `TagConfig` editor (no `TagConfigEditorMap` entry needed) — the picker just fills `FullName`. The Galaxy picker (`GalaxyAddressPickerBody`) needs the selected gateway's `DriverConfig` to open a live browse session via `IBrowserSessionService.OpenAsync("GalaxyMxGateway", configJson)` (same call the old `AliasTagModal` made). **Step 1:** Add the `GalaxyMxGateway` → `GalaxyAddressPickerBody` case to the picker dispatch, mirroring how Modbus/S7 pickers are wired and how the old `AliasTagModal` built the result (`tag.attribute` dot-join → `{"FullName":...}`). **Step 2:** Confirm `LoadTagDriversForEquipmentAsync` surfaces Galaxy drivers + their config to the modal; extend the returned shape if needed. **Step 3:** Build AdminUI: ```bash dotnet build src/Server/ZB.MOM.WW.OtOpcUa.AdminUI ``` **Step 4:** Commit. (Razor behavior is proven live in Task 17 — no bUnit.) **blockedBy:** Task 12. --- ### Task 15: NamespaceEdit.razor — hide the SystemPlatform option **Classification:** small **Estimated implement time:** ~2 min **Parallelizable with:** Tasks 13, 14 **Files:** - Modify: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Clusters/NamespaceEdit.razor` (the `NamespaceKind` selector — drop `SystemPlatform`; offer only `Equipment`, with `Simulated` reserved/hidden as it is today) **Context:** With the enum value removed (Task 9) this likely already won't compile if it enumerates the value; this task makes the selector explicit and clean. **Step 1:** Remove `SystemPlatform` from the kind dropdown / any `Enum.GetValues` binding; default new namespaces to `Equipment`. **Step 2:** Build AdminUI; commit. **blockedBy:** Task 9. --- ### Task 16: Docs — Uns.md + CLAUDE.md **Classification:** small **Estimated implement time:** ~4 min **Parallelizable with:** Tasks 13, 14, 15 **Files:** - Modify: `docs/Uns.md` (remove the alias-tag / relay-converter sections; note Galaxy tags are ordinary equipment tags bound to `GalaxyMxGateway`) - Modify: `CLAUDE.md` (rewrite the "Alias tags" paragraph in the Architecture section + the SystemPlatform/Galaxy notes — Galaxy is now a standard Equipment driver; no `NamespaceKind.SystemPlatform`, no alias/relay machinery) **Context:** Keep docs honest with the new model. Do not echo any secret. **Step 1:** Edit both docs. Cross-reference the design doc `docs/plans/2026-06-12-galaxy-standard-driver-design.md`. **Step 2:** Commit. --- ### Task 17: Verify — build, test, user-driven live `/run` **Classification:** verification **Estimated implement time:** ~5 min (agent) + user live step **Parallelizable with:** none **Files:** none (verification) **Step 1:** Full solution build + the touched test suites: ```bash dotnet build ZB.MOM.WW.OtOpcUa.slnx dotnet test tests/Core/ZB.MOM.WW.OtOpcUa.Configuration.Tests dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests dotnet test tests/Core/ZB.MOM.WW.OtOpcUa.Core.Tests ``` Expected: build 0 errors; all green. **Step 2 (USER drives — the agent does NOT sign in):** Hand the user the live-verify checklist: - Reseed a docker-dev rig: a cluster with **one Equipment namespace**, a `GalaxyMxGateway` driver bound to it, one equipment tag authored via the standard `TagModal` + Galaxy picker (`{"FullName":"."}`). - Confirm the **alias UI is gone** (no "Add alias" button, no `/uns/convert-relays`). - Confirm the Galaxy equipment tag shows a **live value** over OPC UA (Client.CLI `read`/`subscribe`). - Confirm an existing **scripted alarm** on that equipment still raises/acks (native Galaxy alarms are Phase B — not expected yet). **Step 3:** When the user confirms green, finish via `superpowers-extended-cc:finishing-a-development-branch` (carry intent toward merge-to-master + push, but confirm given the user-driven live step). **blockedBy:** all prior tasks. --- ## Out of scope (future plans) - **Phase B** — native `IAlarmSource` alarms on the equipment-tag path (port the `GenericDriverNodeManager` forwarder into `MaterialiseEquipmentTags`). - **Phase C** — server-side `HistoryRead` backend over the Wonderware Historian reader; `IsHistorized` → UA `Historizing`/`HistoryRead` attributes; tag→tagname mapping.