# Galaxy Repository Upstream Gaps + Full Adoption — Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. **Goal:** Close the two upstream gaps in `ZB.MOM.WW.GalaxyRepository`, publish `0.2.0`, then swap mxaccessgw onto the package and delete its inline Galaxy code — one Galaxy-browse implementation across both sidecars. **Architecture:** Two additive, backward-compatible lib changes (alarm-attribute discovery; an injectable `IGalaxyBrowseScopeProvider` for per-identity browse-subtree scoping, default no-op). The dashboard summary stays host-side, recomputed from the lib's cache entry. mxaccessgw registers a `GatewayBrowseScopeProvider` that reads its API-key constraints, then maps the shared gRPC service. HistorianGateway (on 0.1.0) is untouched. **Tech Stack:** .NET 10, C#, gRPC (Grpc.AspNetCore), xUnit, Gitea NuGet feed. Both repos build/test on macOS; the mxaccessgw net48 x86 worker is not touched. **Companion design:** `docs/plans/2026-06-25-galaxyrepository-upstream-gaps-design.md`. Gap evidence: `A2-galaxyrepository-adoption-handoff.md`. **Repos:** - Lib: `~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository` (git repo on `main`; branch for this work). - mxaccessgw: `~/Desktop/MxAccessGateway` (already on `feat/galaxyrepository-adoption`). --- ## Phase 1 — Upstream lib (`ZB.MOM.WW.GalaxyRepository` → 0.2.0) ### Task 1: Branch lib + alarm-attribute row & mapping (TDD) **Classification:** small **Estimated implement time:** ~5 min **Parallelizable with:** Task 3 **Files:** - Create branch in `~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository` - Create: `src/ZB.MOM.WW.GalaxyRepository/GalaxyAlarmAttributeRow.cs` - Modify: `src/ZB.MOM.WW.GalaxyRepository/ZB.MOM.WW.GalaxyRepository.csproj` (add `InternalsVisibleTo`) - Modify: `src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs` (add `internal static MapAlarmRow`) - Test: `tests/ZB.MOM.WW.GalaxyRepository.Tests/GalaxyAlarmAttributeMappingTests.cs` **Step 1: Branch** ```bash git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository checkout -b feat/galaxy-0.2.0-mxaccessgw-gaps ``` **Step 2: Add `InternalsVisibleTo`** to the `` or a new `` in the lib csproj: ```xml ``` **Step 3: Write the failing test** — port from mxaccessgw `src/ZB.MOM.WW.MxGateway.Tests/Galaxy/GalaxyAlarmAttributeMappingTests.cs` (read it first), changing namespace to `ZB.MOM.WW.GalaxyRepository.Tests` and the SUT namespace to `ZB.MOM.WW.GalaxyRepository`. It asserts `GalaxyRepository.MapAlarmRow(fullTagReference, sourceObjectReference, area)` sets `FullTagReference`/`SourceObjectReference`/`Area` and `AckCommentSubtag == string.Empty`. **Step 4: Run — expect FAIL (compile error: types missing)** ```bash dotnet test ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests --filter FullyQualifiedName~GalaxyAlarmAttributeMapping ``` **Step 5: Create `GalaxyAlarmAttributeRow.cs`** — port verbatim from mxaccessgw `Galaxy/GalaxyAlarmAttributeRow.cs`, namespace `ZB.MOM.WW.GalaxyRepository`, keep XML docs. Public sealed record/class with 4 `public string … { get; init; } = string.Empty;` props: `FullTagReference`, `SourceObjectReference`, `Area`, `AckCommentSubtag`. **Step 6: Add `MapAlarmRow`** to `GalaxyRepository.cs` (near `MapRow`/`MapAttributeRow`): ```csharp internal static GalaxyAlarmAttributeRow MapAlarmRow( string fullTagReference, string sourceObjectReference, string area) => new() { FullTagReference = fullTagReference, SourceObjectReference = sourceObjectReference, Area = area, AckCommentSubtag = string.Empty, }; ``` **Step 7: Run — expect PASS.** Then `dotnet build` the lib (zero warnings; `GenerateDocumentationFile=true`). **Step 8: Commit** ```bash git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository add -A git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository commit -m "feat: add GalaxyAlarmAttributeRow + MapAlarmRow" ``` --- ### Task 2: `GetAlarmAttributesAsync` (interface + impl + SQL) **Classification:** small **Estimated implement time:** ~5 min **Parallelizable with:** none (edits `GalaxyRepository.cs` after Task 1) **Files:** - Modify: `src/ZB.MOM.WW.GalaxyRepository/IGalaxyRepository.cs` - Modify: `src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs` **Step 1: Add to `IGalaxyRepository`** (XML-documented): ```csharp /// Returns the alarm-bearing attributes across deployed Galaxy objects. Task> GetAlarmAttributesAsync(CancellationToken ct = default); ``` **Step 2: Add the impl + SQL to `GalaxyRepository`** — port verbatim from mxaccessgw `Galaxy/GalaxyRepository.cs:124-142` (impl) and `:328-371` (`AlarmAttributesSql`). The impl opens a `SqlConnection(options.ConnectionString)`, sets `CommandTimeout = options.CommandTimeoutSeconds`, runs `AlarmAttributesSql`, and per row calls `MapAlarmRow(reader.GetString(0), reader.GetString(1), reader.GetString(2))`. The SQL (3 output columns `full_tag_reference`, `source_object_reference`, `area_name`): ```sql ;WITH deployed_package_chain AS ( SELECT g.gobject_id, p.package_id, p.derived_from_package_id, 0 AS depth FROM gobject g INNER JOIN package p ON p.package_id = g.deployed_package_id WHERE g.is_template = 0 AND g.deployed_package_id <> 0 UNION ALL SELECT dpc.gobject_id, p.package_id, p.derived_from_package_id, dpc.depth + 1 FROM deployed_package_chain dpc INNER JOIN package p ON p.package_id = dpc.derived_from_package_id WHERE dpc.derived_from_package_id <> 0 AND dpc.depth < 10 ), candidate AS ( SELECT dpc.gobject_id, g.tag_name, da.attribute_name, dpc.depth FROM deployed_package_chain dpc INNER JOIN dynamic_attribute da ON da.package_id = dpc.package_id INNER JOIN gobject g ON g.gobject_id = dpc.gobject_id INNER JOIN template_definition td ON td.template_definition_id = g.template_definition_id WHERE td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26) AND da.attribute_name NOT LIKE '[_]%' AND da.attribute_name NOT LIKE '%.Description' AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24) ), ranked AS ( SELECT c.*, ROW_NUMBER() OVER ( PARTITION BY c.gobject_id, c.attribute_name ORDER BY c.depth) AS rn FROM candidate c ) SELECT r.tag_name + '.' + r.attribute_name AS full_tag_reference, r.tag_name AS source_object_reference, ISNULL(area.tag_name, '') AS area_name FROM ranked r INNER JOIN gobject g ON g.gobject_id = r.gobject_id LEFT JOIN gobject area ON area.gobject_id = g.area_gobject_id WHERE r.rn = 1 AND EXISTS ( SELECT 1 FROM deployed_package_chain dpc2 INNER JOIN primitive_instance pi ON pi.package_id = dpc2.package_id AND pi.primitive_name = r.attribute_name INNER JOIN primitive_definition pd ON pd.primitive_definition_id = pi.primitive_definition_id AND pd.primitive_name = 'AlarmExtension' WHERE dpc2.gobject_id = r.gobject_id ) ORDER BY r.tag_name, r.attribute_name ``` Match the exact connection/reader idiom already used by `GetAttributesAsync` in the same file (copy its structure). **Step 3: Build** — `dotnet build` the lib, zero warnings. **Step 4: Commit** ```bash git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository commit -am "feat: add IGalaxyRepository.GetAlarmAttributesAsync + AlarmAttributesSql" ``` (SQL execution is only verifiable against a live Galaxy DB — covered by mxaccessgw's opt-in `IntegrationTests/Galaxy`, run later. Unit coverage is `MapAlarmRow` from Task 1.) --- ### Task 3: `IGalaxyBrowseScopeProvider` + null impl + DI registration **Classification:** small **Estimated implement time:** ~4 min **Parallelizable with:** Task 1 **Files:** - Create: `src/ZB.MOM.WW.GalaxyRepository/Grpc/IGalaxyBrowseScopeProvider.cs` - Create: `src/ZB.MOM.WW.GalaxyRepository/Grpc/NullGalaxyBrowseScopeProvider.cs` - Modify: `src/ZB.MOM.WW.GalaxyRepository/DependencyInjection/GalaxyRepositoryServiceCollectionExtensions.cs` **Step 1: Interface** ```csharp using Grpc.Core; namespace ZB.MOM.WW.GalaxyRepository.Grpc; /// /// Resolves the browse-subtree glob patterns the current caller is allowed to see. /// Lets a hosting gateway scope results per /// identity without the library knowing the host's authorization model. The default /// applies no scoping (full hierarchy). /// public interface IGalaxyBrowseScopeProvider { /// /// Returns the allowed browse-subtree globs for the current call, or /// /empty for no restriction (full hierarchy). /// IReadOnlyList? ResolveBrowseSubtrees(ServerCallContext context); } ``` **Step 2: Null impl** ```csharp using Grpc.Core; namespace ZB.MOM.WW.GalaxyRepository.Grpc; /// Default that applies no scoping. public sealed class NullGalaxyBrowseScopeProvider : IGalaxyBrowseScopeProvider { /// public IReadOnlyList? ResolveBrowseSubtrees(ServerCallContext context) => null; } ``` **Step 3: Register** in `AddZbGalaxyRepository` (use `TryAddSingleton` so a host override wins; add `using Microsoft.Extensions.DependencyInjection.Extensions;` and `using ZB.MOM.WW.GalaxyRepository.Grpc;`): ```csharp services.TryAddSingleton(); ``` **Step 4: Build** — zero warnings. **Step 5: Commit** ```bash git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository commit -am "feat: add IGalaxyBrowseScopeProvider (default no-op) + registration" ``` --- ### Task 4: Wire scope provider into the lib gRPC service (TDD) **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** none (depends on Task 3; edits the gRPC service) **Files:** - Modify: `src/ZB.MOM.WW.GalaxyRepository/Grpc/GalaxyRepositoryGrpcService.cs` - Create: `tests/ZB.MOM.WW.GalaxyRepository.Tests/GalaxyRepositoryGrpcServiceScopeTests.cs` - Maybe modify: `tests/ZB.MOM.WW.GalaxyRepository.Tests/Fakes.cs` (add a fake scope provider + a `ServerCallContext` test double if not present) **Step 1: Write failing tests** — model on mxaccessgw `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Grpc/GalaxyRepositoryGrpcServiceTests.cs` (read it for the cache-entry fixture + `TestServerCallContext` pattern). Two cases: 1. `DiscoverHierarchy_DefaultScope_ReturnsFullHierarchy` — provider returns `null` → all objects (current behavior). 2. `BrowseChildren_ScopedProvider_FiltersChildren` — provider returns `["NonExistent"]` → empty children (mirrors mxaccessgw `BrowseChildren_BrowseSubtreesConstraint_FiltersChildren`). Construct the service with a fake `IGalaxyBrowseScopeProvider`. **Step 2: Run — expect FAIL** (ctor has no provider param yet): ```bash dotnet test ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests --filter FullyQualifiedName~GalaxyRepositoryGrpcServiceScope ``` **Step 3: Inject + thread globs** in `GalaxyRepositoryGrpcService`: - Add ctor param `IGalaxyBrowseScopeProvider scope` (4th dependency). - `DiscoverHierarchy`: `IReadOnlyList? browseSubtrees = scope.ResolveBrowseSubtrees(context);` then pass `browseSubtrees` (instead of `null`) to `ComputeFilterSignature` and `Project`. - `BrowseChildren`: same — pass `browseSubtrees` to `ComputeFilterSignature(request, browseSubtrees, parentId)` and `ProjectChildren`. - `WatchDeployEvents`: resolve once before the loop; pass to a now-instance `MapDeployEvent(info, browseSubtrees)`. - Restore the scoped-count `MapDeployEvent` from mxaccessgw `Grpc/GalaxyRepositoryGrpcService.cs:224-253`: when `browseSubtrees is { Count: > 0 } && cache.Current.HasData`, re-project the whole hierarchy scoped to the globs and override `objectCount`/`attributeCount`. (Reference the mxaccessgw body verbatim.) **Step 4: Run — expect PASS.** Build the lib, zero warnings. **Step 5: Commit** ```bash git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository commit -am "feat: scope GalaxyRepositoryGrpcService results via IGalaxyBrowseScopeProvider" ``` --- ### Task 5: Bump version 0.2.0 + full lib build & test **Classification:** trivial **Estimated implement time:** ~3 min **Parallelizable with:** none **Files:** - Modify: `~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository/Directory.Build.props` (`0.1.0` → `0.2.0`) **Step 1: Edit version.** **Step 2: Full verify** ```bash dotnet build ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository/ZB.MOM.WW.GalaxyRepository.slnx -c Release dotnet test ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests ``` Expected: build zero-warning, all tests pass. **Step 3: Commit** ```bash git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository commit -am "chore: bump ZB.MOM.WW.GalaxyRepository to 0.2.0" ``` --- ### Task 6: Pack + publish 0.2.0 to Gitea + verify **Classification:** high-risk (outward action — publishes a package) **Estimated implement time:** ~4 min **Parallelizable with:** none (gates all of Phase 2) **Files:** none (build artifacts only) **Step 1: Pack** ```bash dotnet pack ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/ZB.MOM.WW.GalaxyRepository.csproj -c Release -o /tmp/galaxy-pack ls /tmp/galaxy-pack/ZB.MOM.WW.GalaxyRepository.0.2.0.nupkg ``` **Step 2: Push to Gitea** (creds auto-sourced from `~/.zshenv`: `GITEA_USERNAME`/`GITEA_TOKEN`; the cargo/python notes don't apply here — this is the NuGet recipe used before): ```bash dotnet nuget push /tmp/galaxy-pack/ZB.MOM.WW.GalaxyRepository.0.2.0.nupkg \ --source "https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json" \ --api-key "$GITEA_TOKEN" ``` **Step 3: Verify it's in the feed** ```bash curl -s -u "$GITEA_USERNAME:$GITEA_TOKEN" \ "https://gitea.dohertylan.com/api/packages/dohertj2/nuget/v3/registration/zb.mom.ww.galaxyrepository/index.json" \ | grep -o '"version":"0.2.0"' && echo "0.2.0 PUBLISHED" ``` Expected: `0.2.0 PUBLISHED`. Do not proceed to Phase 2 until this confirms. **Step 4: Push the lib branch** (optional, ask first per repo norms): ```bash git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository push -u origin feat/galaxy-0.2.0-mxaccessgw-gaps ``` --- ## Phase 2 — mxaccessgw adoption (branch `feat/galaxyrepository-adoption`) ### Task 7: nuget.config + PackageReference + restore **Classification:** small **Estimated implement time:** ~4 min **Parallelizable with:** none (gates Task 8/9). Depends on Task 6. **Files:** - Modify: `nuget.config` (repo root) - Modify: `src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj` **Step 1:** Add under the `dohertj2-gitea` `` in `nuget.config`: ```xml ``` **Step 2:** Add to the Server `.csproj` `` of PackageReferences: ```xml ``` (If the repo uses central package management, add the version to `Directory.Packages.props` instead and reference without `Version=`. Check `src/Directory.Packages.props` first.) **Step 3: Restore + build** (inline Galaxy still present — namespaces differ, so it compiles with the package unused): ```bash dotnet restore src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj dotnet build src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj ``` Expected: 0.2.0 restores from Gitea; build green. **Step 4: Commit** ```bash git add nuget.config src/ZB.MOM.WW.MxGateway.Server/*.csproj src/Directory.Packages.props git commit -m "build(gateway): add ZB.MOM.WW.GalaxyRepository 0.2.0 package reference" ``` --- ### Task 8: Host-side dashboard-summary projector (TDD) **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 9 prep (different files). Depends on Task 7. **Files:** - Create: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardGalaxySummaryProjector.cs` - Test: `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGalaxySummaryProjectorTests.cs` **Step 1: Write failing test** — given a lib `ZB.MOM.WW.GalaxyRepository.GalaxyHierarchyCacheEntry` with a couple of `GalaxyObject`s across two templates/categories and `Status = Healthy`, `Project(entry)` returns a `DashboardGalaxySummary` with mapped `Status`, the 5 counts copied, `TopTemplates` grouped/ordered by instance count, and `ObjectCategories` grouped with resolved names. **Step 2: Run — expect FAIL.** **Step 3: Implement** `DashboardGalaxySummaryProjector.Project(GalaxyHierarchyCacheEntry entry) → DashboardGalaxySummary` — port `BuildDashboardSummary`, `MapDashboardStatus`, `ResolveCategoryName` out of mxaccessgw `Galaxy/GalaxyHierarchyCache.cs` (read those methods first). Source the counts/timestamps/status from the lib entry; derive `TopTemplates`/`ObjectCategories` by grouping `entry.Objects`. `using ZB.MOM.WW.GalaxyRepository;`. **Step 4: Run — expect PASS.** Build Server. **Step 5: Commit** ```bash git commit -am "feat(dashboard): host-side Galaxy summary projector over lib cache entry" ``` --- ### Task 9: The swap — DI rewire, scope provider, delete inline, rebind namespaces **Classification:** high-risk **Estimated implement time:** ~5 min per sub-area; SPLIT during execution into 9a–9e, but they land as one green build. Depends on Task 7, Task 8. **Files:** - Create: `src/ZB.MOM.WW.MxGateway.Server/Security/Authorization/GatewayBrowseScopeProvider.cs` - Modify: `src/ZB.MOM.WW.MxGateway.Server/GatewayApplication.cs:11,95,196` - Delete: the inline 1:1 set under `src/ZB.MOM.WW.MxGateway.Server/Galaxy/` — `GalaxyAlarmAttributeRow.cs`, `GalaxyBrowseChildrenResult.cs`, `GalaxyBrowseProjector.cs`, `GalaxyCacheStatus.cs`, `GalaxyDeployEventInfo.cs`, `GalaxyDeployNotifier.cs`, `GalaxyGlobMatcher.cs`, `GalaxyHierarchyCache.cs`, `GalaxyHierarchyCacheEntry.cs`, `GalaxyHierarchyIndex.cs`, `GalaxyHierarchyProjector.cs`, `GalaxyHierarchyQueryResult.cs`, `GalaxyHierarchyRefreshService.cs`, `GalaxyHierarchyRow.cs`, `GalaxyHierarchySnapshot.cs`, `GalaxyHierarchySnapshotStore.cs`, `GalaxyObjectView.cs`, `GalaxyRepository.cs`, `GalaxyRepositoryOptions.cs`, `GalaxyRepositoryServiceCollectionExtensions.cs`, `GalaxyTagLookup.cs`, `IGalaxyDeployNotifier.cs`, `IGalaxyHierarchyCache.cs`, `IGalaxyHierarchySnapshotStore.cs`, `IGalaxyRepository.cs` - Delete: `src/ZB.MOM.WW.MxGateway.Server/Grpc/GalaxyRepositoryGrpcService.cs`, `src/ZB.MOM.WW.MxGateway.Server/Grpc/GalaxyProtoMapper.cs` - Modify (rebind `using ZB.MOM.WW.MxGateway.Server.Galaxy;` → `using ZB.MOM.WW.GalaxyRepository;`, and for proto-mapper users → `using ZB.MOM.WW.GalaxyRepository.Grpc;`): `Alarms/AlarmWatchListResolver.cs`, `Dashboard/DashboardGalaxyProjector.cs`, `Dashboard/DashboardBrowseService.cs`, `Dashboard/DashboardSnapshotService.cs`, `Security/Authorization/ConstraintEnforcer.cs`, `Sessions/ArrayAddressNormalizer.cs` (run `grep -rl "Server.Galaxy" src/ZB.MOM.WW.MxGateway.Server` to catch all). **Sub-steps (ordered; build only at the end):** **9a — Scope provider:** ```csharp using Grpc.Core; using ZB.MOM.WW.GalaxyRepository.Grpc; using ZB.MOM.WW.MxGateway.Server.Security.Authentication; namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization; /// Scopes Galaxy browse results to the calling API key's BrowseSubtrees constraint. public sealed class GatewayBrowseScopeProvider(IGatewayRequestIdentityAccessor identityAccessor) : IGalaxyBrowseScopeProvider { /// public IReadOnlyList? ResolveBrowseSubtrees(ServerCallContext context) { ApiKeyConstraints constraints = identityAccessor.Current?.EffectiveConstraints ?? ApiKeyConstraints.Empty; return constraints.BrowseSubtrees; } } ``` **9b — DI rewire** in `GatewayApplication.cs`: replace `using ...Server.Galaxy;` (line 11) with `using ZB.MOM.WW.GalaxyRepository; using ZB.MOM.WW.GalaxyRepository.DependencyInjection;`. Replace line 95 `builder.Services.AddGalaxyRepository();` with: ```csharp builder.Services.AddZbGalaxyRepository(builder.Configuration, "MxGateway:Galaxy"); builder.Services.AddSingleton(); builder.Services.AddSingleton(); ``` Replace line 196 `endpoints.MapGrpcService();` with `endpoints.MapZbGalaxyRepository();`. Preserve Galaxy options validation: confirm `GatewayOptionsValidator` covers `MxGateway:Galaxy` (it did via the inline `.ValidateOnStart()`); if validation was only DataAnnotations on the inline options, re-add `.AddOptions().Bind(...).ValidateOnStart()` against the lib type, or fold the checks into `GatewayOptionsValidator`. Set an explicit `MxGateway:Galaxy:SnapshotCachePath` in `appsettings.json` (lib default is empty → persistence no-ops). **9c — Dashboard consumers:** rebind namespaces; where `DashboardGalaxyProjector`/`DashboardSnapshotService` read `entry.DashboardSummary`, call the injected `DashboardGalaxySummaryProjector.Project(cache.Current)` instead (the lib entry has no `DashboardSummary` member). **9d — Delete inline files** (list above) + rebind remaining consumers (`AlarmWatchListResolver`, `ConstraintEnforcer`, `ArrayAddressNormalizer`). **9e — Build Server** ```bash dotnet build src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj ``` Expected: zero warnings/errors. Fix rebind misses surfaced by the compiler. **Step: Commit** ```bash git add -A git commit -m "refactor(gateway): adopt ZB.MOM.WW.GalaxyRepository; delete inline Galaxy code" ``` --- ### Task 10: Reconcile tests — delete duplicates, keep host-specific, run targeted **Classification:** standard **Estimated implement time:** ~5 min. Depends on Task 9. **Files:** - Delete: `src/ZB.MOM.WW.MxGateway.Tests/Galaxy/GalaxyHierarchyCacheTests.cs`, `GalaxyHierarchyProjectorTests.cs`, `GalaxyHierarchySnapshotStoreTests.cs`, `GalaxyProtoMapperTests.cs`, `GalaxyHierarchyIndexTests.cs`, `GalaxyAlarmAttributeMappingTests.cs` (now upstream) - Keep/adjust: `GalaxyFilterInputSafetyTests.cs`, `Gateway/Grpc/GalaxyRepositoryGrpcServiceTests.cs` (now constructs the lib service + `GatewayBrowseScopeProvider`; verifies per-key filtering end-to-end), the new `DashboardGalaxySummaryProjectorTests.cs`, and any `AlarmWatchListResolver` test. **Step 1:** Delete the duplicated tests. **Step 2:** Rebind/rewire the kept tests to the lib types + scope provider. **Step 3:** Run targeted: ```bash dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj \ --filter "FullyQualifiedName~Galaxy|FullyQualifiedName~Alarm|FullyQualifiedName~Constraint|FullyQualifiedName~Dashboard" ``` Expected: all pass. **Step 4: Commit** ```bash git commit -am "test(gateway): drop Galaxy tests owned upstream; rewire kept tests to the package" ``` --- ### Task 11: Docs + final verification **Classification:** small **Estimated implement time:** ~5 min. Depends on Task 10. **Files:** - Modify: `A2-galaxyrepository-adoption-handoff.md` (mark adopted), `stillpending.md` §2 (mark resolved), `CLAUDE.md` (Galaxy now from the package, if it describes the inline path) **Step 1:** Update the docs to state the adoption shipped (lib 0.2.0, browse-subtree provider, dashboard summary host-side). Note the cross-repo follow-ups (HistorianGateway `pending.md` §A2, scadaproj normalization index) for the next session — don't edit other repos here unless asked. **Step 2: Final build + targeted tests** ```bash dotnet build src/ZB.MOM.WW.MxGateway.Server/ZB.MOM.WW.MxGateway.Server.csproj dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter "FullyQualifiedName~Galaxy|FullyQualifiedName~Alarm|FullyQualifiedName~Dashboard" ``` Expected: green. **Step 3: Commit** ```bash git commit -am "docs: record Galaxy library adoption (0.2.0) complete" ``` --- ## Verification matrix | Layer | Command | Host | |---|---|---| | Lib build+test | `dotnet build … -c Release`; `dotnet test …Tests` | macOS | | Lib publish | `dotnet pack` + `dotnet nuget push` + Gitea API check | macOS | | Gateway Server build | `dotnet build src/ZB.MOM.WW.MxGateway.Server` | macOS | | Gateway targeted tests | `dotnet test …Tests --filter Galaxy\|Alarm\|Constraint\|Dashboard` | macOS | | Live Galaxy SQL (opt-in) | `MXGATEWAY_RUN_LIVE_GALAXY_TESTS=1 dotnet test …IntegrationTests` | needs SQL | The net48 x86 worker is not touched; no Windows build required. ## Risks & notes - **Publish ordering:** Task 6 must confirm 0.2.0 in the feed before Task 7 restore. - **Task 9 is the fragile one:** intermediate states don't compile (deleting the inline namespace breaks consumers until rebound) — do 9a–9e then one build; let the compiler drive the rebind. - **Central package management:** check `src/Directory.Packages.props` before adding the version in Task 7. - **Options validation parity:** don't lose Galaxy options validation in the DI swap (Task 9b). - **`SnapshotCachePath`:** must be set explicitly in mxaccessgw config or snapshot persistence silently no-ops. - Do **not** modify HistorianGateway; 0.2.0 is forward-compatible on 0.1.0.