From 1c4052ee8334452699b1c56323d50362ff63a44e Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 12 Jun 2026 21:05:45 -0400 Subject: [PATCH] docs(galaxy): Phase A implementation plan (Galaxy standard driver) 18-task plan to make GalaxyMxGateway an Equipment-kind driver: retire the SystemPlatform NamespaceKind split + mirror + alias/relay machinery, author Galaxy points as ordinary equipment tags via the standard TagModal. Mostly deletion + a single EF migration dropping the per-kind unique constraint. Phases B (native alarms) + C (server historian) remain out of scope. Co-located .tasks.json for resume. --- ...26-06-12-galaxy-standard-driver-phase-a.md | 671 ++++++++++++++++++ ...laxy-standard-driver-phase-a.md.tasks.json | 24 + 2 files changed, 695 insertions(+) create mode 100644 docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md create mode 100644 docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md.tasks.json diff --git a/docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md b/docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md new file mode 100644 index 00000000..5677d85e --- /dev/null +++ b/docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md @@ -0,0 +1,671 @@ +# 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. diff --git a/docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md.tasks.json b/docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md.tasks.json new file mode 100644 index 00000000..eba48877 --- /dev/null +++ b/docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md.tasks.json @@ -0,0 +1,24 @@ +{ + "planPath": "docs/plans/2026-06-12-galaxy-standard-driver-phase-a.md", + "tasks": [ + {"id": 314, "subject": "Task 0: Create feature branch", "status": "pending"}, + {"id": 315, "subject": "Task 1: DriverTypeRegistry — Galaxy → Equipment", "status": "pending", "blockedBy": [314]}, + {"id": 316, "subject": "Task 2: DraftValidator — drop Galaxy/SystemPlatform branches", "status": "pending", "blockedBy": [314]}, + {"id": 317, "subject": "Task 3: Phase7Composer — remove Galaxy mirror producer + exception clause", "status": "pending", "blockedBy": [314]}, + {"id": 318, "subject": "Task 4: Phase7CompositionResult — remove GalaxyTags + GalaxyTagPlan", "status": "pending", "blockedBy": [317]}, + {"id": 319, "subject": "Task 5: DeploymentArtifact — remove Galaxy mirror decode + isGalaxyAlias", "status": "pending", "blockedBy": [318]}, + {"id": 320, "subject": "Task 6: Phase7Applier + OpcUaPublishActor — delete MaterialiseGalaxyTags", "status": "pending", "blockedBy": [318]}, + {"id": 321, "subject": "Task 7: Authorization — remove ScopeKind.SystemPlatform + WalkSystemPlatform", "status": "pending", "blockedBy": [318]}, + {"id": 322, "subject": "Task 8: Composer↔Artifact byte-parity test (Galaxy equipment tag)", "status": "pending", "blockedBy": [317, 319]}, + {"id": 323, "subject": "Task 9: CAPSTONE — remove SystemPlatform enum values; build green", "status": "pending", "blockedBy": [315, 316, 317, 319, 320, 321]}, + {"id": 324, "subject": "Task 10: EF migration — drop per-kind unique constraint", "status": "pending", "blockedBy": [323]}, + {"id": 325, "subject": "Task 11: Delete alias-only AdminUI files", "status": "pending", "blockedBy": [314]}, + {"id": 326, "subject": "Task 12: UnsTreeService/IUnsTreeService/EquipmentChildRows — strip alias/relay", "status": "pending", "blockedBy": [325]}, + {"id": 327, "subject": "Task 13: EquipmentPage.razor — strip alias UI", "status": "pending", "blockedBy": [326]}, + {"id": 328, "subject": "Task 14: TagModal.razor — wire Galaxy address picker", "status": "pending", "blockedBy": [326]}, + {"id": 329, "subject": "Task 15: NamespaceEdit.razor — hide SystemPlatform option", "status": "pending", "blockedBy": [323]}, + {"id": 330, "subject": "Task 16: Docs — Uns.md + CLAUDE.md", "status": "pending", "blockedBy": [314]}, + {"id": 331, "subject": "Task 17: Verify — build, test, user-driven live /run", "status": "pending", "blockedBy": [322, 324, 327, 328, 329, 330]} + ], + "lastUpdated": "2026-06-12" +}