25 KiB
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 onmain; branch for this work). - mxaccessgw:
~/Desktop/MxAccessGateway(already onfeat/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(addInternalsVisibleTo) - Modify:
src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs(addinternal static MapAlarmRow) - Test:
tests/ZB.MOM.WW.GalaxyRepository.Tests/GalaxyAlarmAttributeMappingTests.cs
Step 1: Branch
git -C ~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository checkout -b feat/galaxy-0.2.0-mxaccessgw-gaps
Step 2: Add InternalsVisibleTo to the <PropertyGroup> or a new <ItemGroup> in the lib csproj:
<ItemGroup>
<InternalsVisibleTo Include="ZB.MOM.WW.GalaxyRepository.Tests" />
</ItemGroup>
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)
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):
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
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):
/// <summary>Returns the alarm-bearing attributes across deployed Galaxy objects.</summary>
Task<List<GalaxyAlarmAttributeRow>> 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):
;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
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
using Grpc.Core;
namespace ZB.MOM.WW.GalaxyRepository.Grpc;
/// <summary>
/// Resolves the browse-subtree glob patterns the current caller is allowed to see.
/// Lets a hosting gateway scope <see cref="GalaxyRepositoryGrpcService"/> results per
/// identity without the library knowing the host's authorization model. The default
/// <see cref="NullGalaxyBrowseScopeProvider"/> applies no scoping (full hierarchy).
/// </summary>
public interface IGalaxyBrowseScopeProvider
{
/// <summary>
/// Returns the allowed browse-subtree globs for the current call, or
/// <see langword="null"/>/empty for no restriction (full hierarchy).
/// </summary>
IReadOnlyList<string>? ResolveBrowseSubtrees(ServerCallContext context);
}
Step 2: Null impl
using Grpc.Core;
namespace ZB.MOM.WW.GalaxyRepository.Grpc;
/// <summary>Default <see cref="IGalaxyBrowseScopeProvider"/> that applies no scoping.</summary>
public sealed class NullGalaxyBrowseScopeProvider : IGalaxyBrowseScopeProvider
{
/// <inheritdoc />
public IReadOnlyList<string>? 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;):
services.TryAddSingleton<IGalaxyBrowseScopeProvider, NullGalaxyBrowseScopeProvider>();
Step 4: Build — zero warnings.
Step 5: Commit
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 + aServerCallContexttest 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:
DiscoverHierarchy_DefaultScope_ReturnsFullHierarchy— provider returnsnull→ all objects (current behavior).BrowseChildren_ScopedProvider_FiltersChildren— provider returns["NonExistent"]→ empty children (mirrors mxaccessgwBrowseChildren_BrowseSubtreesConstraint_FiltersChildren). Construct the service with a fakeIGalaxyBrowseScopeProvider.
Step 2: Run — expect FAIL (ctor has no provider param yet):
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<string>? browseSubtrees = scope.ResolveBrowseSubtrees(context);then passbrowseSubtrees(instead ofnull) toComputeFilterSignatureandProject.BrowseChildren: same — passbrowseSubtreestoComputeFilterSignature(request, browseSubtrees, parentId)andProjectChildren.WatchDeployEvents: resolve once before the loop; pass to a now-instanceMapDeployEvent(info, browseSubtrees).- Restore the scoped-count
MapDeployEventfrom mxaccessgwGrpc/GalaxyRepositoryGrpcService.cs:224-253: whenbrowseSubtrees is { Count: > 0 } && cache.Current.HasData, re-project the whole hierarchy scoped to the globs and overrideobjectCount/attributeCount. (Reference the mxaccessgw body verbatim.)
Step 4: Run — expect PASS. Build the lib, zero warnings.
Step 5: Commit
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(<Version>0.1.0</Version>→0.2.0)
Step 1: Edit version.
Step 2: Full verify
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
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
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):
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
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):
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 <packageSource> in nuget.config:
<package pattern="ZB.MOM.WW.GalaxyRepository" />
Step 2: Add to the Server .csproj <ItemGroup> of PackageReferences:
<PackageReference Include="ZB.MOM.WW.GalaxyRepository" Version="0.2.0" />
(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):
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
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 GalaxyObjects 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
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(rungrep -rl "Server.Galaxy" src/ZB.MOM.WW.MxGateway.Serverto catch all).
Sub-steps (ordered; build only at the end):
9a — Scope provider:
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;
/// <summary>Scopes Galaxy browse results to the calling API key's BrowseSubtrees constraint.</summary>
public sealed class GatewayBrowseScopeProvider(IGatewayRequestIdentityAccessor identityAccessor)
: IGalaxyBrowseScopeProvider
{
/// <inheritdoc />
public IReadOnlyList<string>? 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:
builder.Services.AddZbGalaxyRepository(builder.Configuration, "MxGateway:Galaxy");
builder.Services.AddSingleton<ZB.MOM.WW.GalaxyRepository.Grpc.IGalaxyBrowseScopeProvider,
Security.Authorization.GatewayBrowseScopeProvider>();
builder.Services.AddSingleton<Dashboard.DashboardGalaxySummaryProjector>();
Replace line 196 endpoints.MapGrpcService<GalaxyRepositoryGrpcService>(); 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<GalaxyRepositoryOptions>().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
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
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 newDashboardGalaxySummaryProjectorTests.cs, and anyAlarmWatchListResolvertest.
Step 1: Delete the duplicated tests. Step 2: Rebind/rewire the kept tests to the lib types + scope provider. Step 3: Run targeted:
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
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
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
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.propsbefore 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.