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.
This commit is contained in:
@@ -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/<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.
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user