Verified the A2 gRPC-service authz-parity question: a wholesale swap to MapZbGalaxyRepository() is unsafe because per-key browse-subtree filtering is baked into mxaccessgw's service body. Records the verdict in the A2 handoff + stillpending §2. Adds the approved design for closing the upstream gaps in ZB.MOM.WW.GalaxyRepository 0.2.0 (alarm-attribute discovery + an injectable browse-subtree scope provider; dashboard summary stays host-side) and the full mxaccessgw adoption.
10 KiB
Galaxy Repository — Upstream Gaps + Full Adoption (Design)
Date: 2026-06-25 · Status: Approved (design) · Scope: full adoption — close the upstream gaps in ZB.MOM.WW.GalaxyRepository, publish 0.2.0, wire mxaccessgw to consume it, delete the inline Galaxy code.
Supersedes the open question in A2-galaxyrepository-adoption-handoff.md and the stillpending.md §2 "Adopt the shared ZB.MOM.WW.GalaxyRepository library" item.
Goal
mxaccessgw and HistorianGateway both browse the Galaxy Repository. The browse implementation was extracted from mxaccessgw into the shared ZB.MOM.WW.GalaxyRepository library, which HistorianGateway already consumes (PackageReference @ 0.1.0). mxaccessgw still runs its own inline copy under src/ZB.MOM.WW.MxGateway.Server/Galaxy/**. This work closes the small set of gaps that prevent mxaccessgw from dropping the inline copy, then swaps mxaccessgw to the package so there is one Galaxy-browse implementation. The galaxy_repository.v1 wire contract is unchanged — no client change.
Gap analysis (evidence-backed)
Two parallel diff audits compared every type/member in mxaccessgw's inline Galaxy surface against the lib. Result: the lib is a near-exact extraction. The divergences are:
Real upstream gaps (must be added to the lib)
-
Alarm-attribute discovery — capability gap. Absent from the lib entirely; consumed by mxaccessgw
Alarms/AlarmWatchListResolver.cs. Four pieces:GalaxyAlarmAttributeRow(public, 4 props:FullTagReference,SourceObjectReference,Area,AckCommentSubtag) —Galaxy/GalaxyAlarmAttributeRow.cs.IGalaxyRepository.GetAlarmAttributesAsync(CancellationToken = default) → Task<List<GalaxyAlarmAttributeRow>>—Galaxy/IGalaxyRepository.cs:37.GalaxyRepository.GetAlarmAttributesAsyncimpl (:124-142),internal static MapAlarmRow(:164-173), privateAlarmAttributesSqlconst (:328-371, a CTE selecting alarm-bearing attributes via theAlarmExtensionprimitive, projectingfull_tag_reference,source_object_reference,area_name).
-
Per-identity browse-subtree scoping hook — service-wiring gap. mxaccessgw's
Grpc/GalaxyRepositoryGrpcService.csinjectsIGatewayRequestIdentityAccessorand threads per-API-key browse-subtree globs (EffectiveConstraints.BrowseSubtrees) intoDiscoverHierarchy(:78-86),BrowseChildren(:126-143), andWatchDeployEvents/MapDeployEventscoped counts (:180,196,224-253). The lib's service hard-codesbrowseSubtreeGlobs: nulland documents that subtree scoping is "the responsibility of the hosting gateway's interceptor layer." The projector overloads already accept the globs publicly — only the service wiring is missing. (Verified 2026-06-25; see the ⚠️ block inA2-galaxyrepository-adoption-handoff.md.)
Not a lib gap (resolved host-side)
- Dashboard summary — coupling, stays host-side. mxaccessgw's
GalaxyHierarchyCacheEntrycarries aDashboardSummary(DashboardGalaxySummary+ status/template/category types); the lib deliberately shed it. No lib change needed: the lib'sGalaxyHierarchyCacheEntryalready exposesObjects,Index, all five counts, status, and timestamps — everyDashboardGalaxySummaryfield (includingTopTemplates/ObjectCategories) is derivable by groupingObjectshost-side. Keeping it out of the lib matches the lib's intent and keeps HistorianGateway dashboard-free.
Not lib gaps (host config, intentional)
GalaxyRepositoryOptions.SectionName("MxGateway:Galaxy"vs lib"GalaxyRepository") — host passes its section path toAddZbGalaxyRepository.SnapshotCachePathdefault (Windows path vs empty) — host must supply an explicit path or persistence no-ops.- Options validator — lib intentionally ships none; mxaccessgw keeps
GatewayOptionsValidator.
Upstream design (ZB.MOM.WW.GalaxyRepository → 0.2.0)
All changes are additive and backward-compatible. HistorianGateway @ 0.1.0 is unaffected; if it ever bumps to 0.2.0 the behavior is identical (default no-op scope provider). HistorianGateway is not modified by this work.
A. Alarm-attribute discovery
Port the four pieces verbatim into the lib (namespace ZB.MOM.WW.GalaxyRepository):
- New
GalaxyAlarmAttributeRow.cs(public, XML-documented). - Add
GetAlarmAttributesAsynctoIGalaxyRepositoryandGalaxyRepository, withMapAlarmRow(internal) andAlarmAttributesSql. - Add
[assembly: InternalsVisibleTo("ZB.MOM.WW.GalaxyRepository.Tests")]if not already present, soMapAlarmRowstays unit-testable. - Port
GalaxyAlarmAttributeMappingTestsinto the lib test project.
B. Browse-subtree scoping hook — Approach (A): injectable provider
- New public interface:
public interface IGalaxyBrowseScopeProvider { IReadOnlyList<string>? ResolveBrowseSubtrees(ServerCallContext context); } - Default
NullGalaxyBrowseScopeProviderreturnsnull(no scoping).AddZbGalaxyRepositoryregisters it viaTryAddSingletonso the lib's current behavior is byte-identical and a consumer can override it. - Inject
IGalaxyBrowseScopeProviderintoGalaxyRepositoryGrpcService. Replace each hard-codednullwithscope.ResolveBrowseSubtrees(context):DiscoverHierarchy/BrowseChildren: pass globs toComputeFilterSignature+Project/ProjectChildren.WatchDeployEvents: resolve once per stream; restore the scoped-countMapDeployEvent(re-projects the hierarchy when globs are present, as mxaccessgw does today).
- Lib tests: (1) default provider → full hierarchy (current behavior); (2) provider returning a glob → filtered hierarchy + scoped deploy-event counts.
Rejected alternatives: protected virtual + subclass (forces consumers to register a subclass instead of mapping the shared service); teaching the lib about API-key constraints (couples the lib to the gateway auth model).
Versioning
Bump Directory.Build.props <Version> 0.1.0 → 0.2.0. Adding a ctor dependency to GalaxyRepositoryGrpcService is source-breaking only for direct constructors; all consumers use DI / MapZbGalaxyRepository(), and the default registration covers it.
Publish
dotnet pack -c Release then dotnet nuget push to the Gitea feed (https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json) with GITEA_TOKEN. Verify the version is listed via the Gitea packages API. Publish before the mxaccessgw restore (mxaccessgw cannot PackageReference 0.2.0 until it exists in the feed).
Host-side design (mxaccessgw adoption)
- nuget.config — add
<package pattern="ZB.MOM.WW.GalaxyRepository" />under thedohertj2-giteapackageSourceMappingsource (restore fails silently otherwise). - Server
.csproj—<PackageReference Include="ZB.MOM.WW.GalaxyRepository" Version="0.2.0" />; remove inline Galaxy compile items (delete the files). - DI / endpoints —
services.AddZbGalaxyRepository(configuration, "MxGateway:Galaxy");endpoints.MapZbGalaxyRepository(); registerGatewayBrowseScopeProvider : IGalaxyBrowseScopeProvider(readsIGatewayRequestIdentityAccessor.Current?.EffectiveConstraints.BrowseSubtrees). DeleteGalaxy/GalaxyRepositoryServiceCollectionExtensions.cs. - Dashboard summary relocation — move
BuildDashboardSummary/MapDashboardStatus/ResolveCategoryNameout of the (now-shared) cache into a host-side projector that computesDashboardGalaxySummaryfromcache.Current(Objects+ counts + status + timestamps).DashboardGalaxySummary+ supporting types remain inServer/Dashboard/. - Keep host-side —
AlarmWatchListResolver(now calls the lib'sGetAlarmAttributesAsync), Galaxy options validation (GatewayOptionsValidator), the SQL readiness health check. - Namespace rebind —
ZB.MOM.WW.MxGateway.Server.Galaxy→ZB.MOM.WW.GalaxyRepositoryin dashboard (DashboardGalaxyProjector,DashboardBrowseService,DashboardSnapshotService), alarms (AlarmWatchListResolver), security (ConstraintEnforcer), sessions (ArrayAddressNormalizer). Confirm each consumed type ispublicin the lib (the audit confirmsGalaxyTagLookup,GalaxyHierarchyIndex.TagsByAddress,GalaxyGlobMatcher.IsMatch,IGalaxyHierarchyCache.Current, projectors all are). - Config — set an explicit
MxGateway:Galaxy:SnapshotCachePath(lib default is empty → persistence no-ops). Keep theMxGateway:Galaxysection path. - Tests — delete duplicated
Tests/Galaxy/**now owned upstream (GalaxyHierarchyCacheTests,GalaxyHierarchyProjectorTests,GalaxyHierarchySnapshotStoreTests,GalaxyProtoMapperTests,GalaxyHierarchyIndexTests). Keep mxaccessgw-specific:GalaxyFilterInputSafetyTests, the wiredGalaxyRepositoryGrpcServiceTestsbrowse-subtree filtering test (now exercises the host scope provider end-to-end), the dashboard-summary projector test, and the liveIntegrationTests/Galaxy/**.
Verification
- Lib:
dotnet build+dotnet test(net10.0) — runs on the Mac. - mxaccessgw Server:
dotnet build src/ZB.MOM.WW.MxGateway.Server+ targeteddotnet test src/ZB.MOM.WW.MxGateway.Tests --filterfor Galaxy / Alarm / Constraint / Dashboard — runs on the Mac. The net48 x86 worker is untouched and not built here. - Live Galaxy SQL tests stay opt-in (
MXGATEWAY_RUN_LIVE_GALAXY_TESTS=1). - Zero-warning build (both repos enforce analyzers; the lib has
GenerateDocumentationFile=true→ XML-doc all new public members).
Sequence & risks
- Lib A + B + lib tests → build/test green.
- Bump
0.2.0, pack, publish to Gitea, verify in feed. - mxaccessgw: nuget.config + csproj + DI + scope provider + dashboard relocation + namespace rebind + delete inline files + delete duplicated tests.
- Build Server + run targeted tests green.
- (Optional) live browse + alarm-watch-list validation; update
A2-galaxyrepository-adoption-handoff.md,stillpending.md§2, HistorianGatewaypending.md§A2, scadaproj normalization index.
Risks: publish-ordering (must publish before mxaccessgw restore); the dashboard-summary relocation is the fiddliest host edit; namespace-rebind churn spans many files; lib GenerateDocumentationFile=true requires XML docs on new public members.
Out of scope
- Modifying HistorianGateway (stays on 0.1.0; additive change is forward-compatible).
- Changing the
galaxy_repository.v1proto / any client. - The session-resilience epic and other
stillpending.mditems.