# 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/<timestamp>_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":"<obj>.<attr>"}`).
- 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.
