galaxy-adoption: verify gRPC authz-parity gate + design upstream gaps (0.2.0)
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.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# 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)
|
||||
|
||||
1. **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.GetAlarmAttributesAsync` impl (`:124-142`), `internal static MapAlarmRow` (`:164-173`), private `AlarmAttributesSql` const (`:328-371`, a CTE selecting alarm-bearing attributes via the `AlarmExtension` primitive, projecting `full_tag_reference`, `source_object_reference`, `area_name`).
|
||||
|
||||
2. **Per-identity browse-subtree scoping hook — service-wiring gap.** mxaccessgw's `Grpc/GalaxyRepositoryGrpcService.cs` injects `IGatewayRequestIdentityAccessor` and threads per-API-key browse-subtree globs (`EffectiveConstraints.BrowseSubtrees`) into `DiscoverHierarchy` (`:78-86`), `BrowseChildren` (`:126-143`), and `WatchDeployEvents`/`MapDeployEvent` scoped counts (`:180,196,224-253`). The lib's service hard-codes `browseSubtreeGlobs: null` and 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 in `A2-galaxyrepository-adoption-handoff.md`.)
|
||||
|
||||
### Not a lib gap (resolved host-side)
|
||||
|
||||
3. **Dashboard summary — coupling, stays host-side.** mxaccessgw's `GalaxyHierarchyCacheEntry` carries a `DashboardSummary` (`DashboardGalaxySummary` + status/template/category types); the lib deliberately shed it. **No lib change needed:** the lib's `GalaxyHierarchyCacheEntry` already exposes `Objects`, `Index`, all five counts, status, and timestamps — every `DashboardGalaxySummary` field (including `TopTemplates` / `ObjectCategories`) is derivable by grouping `Objects` host-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 to `AddZbGalaxyRepository`.
|
||||
- `SnapshotCachePath` default (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 `GetAlarmAttributesAsync` to `IGalaxyRepository` and `GalaxyRepository`, with `MapAlarmRow` (internal) and `AlarmAttributesSql`.
|
||||
- Add `[assembly: InternalsVisibleTo("ZB.MOM.WW.GalaxyRepository.Tests")]` if not already present, so `MapAlarmRow` stays unit-testable.
|
||||
- Port `GalaxyAlarmAttributeMappingTests` into the lib test project.
|
||||
|
||||
### B. Browse-subtree scoping hook — **Approach (A): injectable provider**
|
||||
|
||||
- New public interface:
|
||||
```csharp
|
||||
public interface IGalaxyBrowseScopeProvider
|
||||
{
|
||||
IReadOnlyList<string>? ResolveBrowseSubtrees(ServerCallContext context);
|
||||
}
|
||||
```
|
||||
- Default `NullGalaxyBrowseScopeProvider` returns `null` (no scoping). `AddZbGalaxyRepository` registers it via `TryAddSingleton` so the lib's current behavior is byte-identical and a consumer can override it.
|
||||
- Inject `IGalaxyBrowseScopeProvider` into `GalaxyRepositoryGrpcService`. Replace each hard-coded `null` with `scope.ResolveBrowseSubtrees(context)`:
|
||||
- `DiscoverHierarchy` / `BrowseChildren`: pass globs to `ComputeFilterSignature` + `Project`/`ProjectChildren`.
|
||||
- `WatchDeployEvents`: resolve once per stream; restore the scoped-count `MapDeployEvent` (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)
|
||||
|
||||
1. **nuget.config** — add `<package pattern="ZB.MOM.WW.GalaxyRepository" />` under the `dohertj2-gitea` `packageSourceMapping` source (restore fails silently otherwise).
|
||||
2. **Server `.csproj`** — `<PackageReference Include="ZB.MOM.WW.GalaxyRepository" Version="0.2.0" />`; remove inline Galaxy compile items (delete the files).
|
||||
3. **DI / endpoints** — `services.AddZbGalaxyRepository(configuration, "MxGateway:Galaxy")`; `endpoints.MapZbGalaxyRepository()`; register `GatewayBrowseScopeProvider : IGalaxyBrowseScopeProvider` (reads `IGatewayRequestIdentityAccessor.Current?.EffectiveConstraints.BrowseSubtrees`). Delete `Galaxy/GalaxyRepositoryServiceCollectionExtensions.cs`.
|
||||
4. **Dashboard summary relocation** — move `BuildDashboardSummary` / `MapDashboardStatus` / `ResolveCategoryName` out of the (now-shared) cache into a host-side projector that computes `DashboardGalaxySummary` from `cache.Current` (`Objects` + counts + status + timestamps). `DashboardGalaxySummary` + supporting types remain in `Server/Dashboard/`.
|
||||
5. **Keep host-side** — `AlarmWatchListResolver` (now calls the lib's `GetAlarmAttributesAsync`), Galaxy options validation (`GatewayOptionsValidator`), the SQL readiness health check.
|
||||
6. **Namespace rebind** — `ZB.MOM.WW.MxGateway.Server.Galaxy` → `ZB.MOM.WW.GalaxyRepository` in dashboard (`DashboardGalaxyProjector`, `DashboardBrowseService`, `DashboardSnapshotService`), alarms (`AlarmWatchListResolver`), security (`ConstraintEnforcer`), sessions (`ArrayAddressNormalizer`). Confirm each consumed type is `public` in the lib (the audit confirms `GalaxyTagLookup`, `GalaxyHierarchyIndex.TagsByAddress`, `GalaxyGlobMatcher.IsMatch`, `IGalaxyHierarchyCache.Current`, projectors all are).
|
||||
7. **Config** — set an explicit `MxGateway:Galaxy:SnapshotCachePath` (lib default is empty → persistence no-ops). Keep the `MxGateway:Galaxy` section path.
|
||||
8. **Tests** — delete duplicated `Tests/Galaxy/**` now owned upstream (`GalaxyHierarchyCacheTests`, `GalaxyHierarchyProjectorTests`, `GalaxyHierarchySnapshotStoreTests`, `GalaxyProtoMapperTests`, `GalaxyHierarchyIndexTests`). Keep mxaccessgw-specific: `GalaxyFilterInputSafetyTests`, the wired `GalaxyRepositoryGrpcServiceTests` browse-subtree filtering test (now exercises the host scope provider end-to-end), the dashboard-summary projector test, and the live `IntegrationTests/Galaxy/**`.
|
||||
|
||||
## Verification
|
||||
|
||||
- **Lib:** `dotnet build` + `dotnet test` (net10.0) — runs on the Mac.
|
||||
- **mxaccessgw Server:** `dotnet build src/ZB.MOM.WW.MxGateway.Server` + targeted `dotnet test src/ZB.MOM.WW.MxGateway.Tests --filter` for 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
|
||||
|
||||
1. Lib A + B + lib tests → build/test green.
|
||||
2. Bump `0.2.0`, pack, publish to Gitea, verify in feed.
|
||||
3. mxaccessgw: nuget.config + csproj + DI + scope provider + dashboard relocation + namespace rebind + delete inline files + delete duplicated tests.
|
||||
4. Build Server + run targeted tests green.
|
||||
5. (Optional) live browse + alarm-watch-list validation; update `A2-galaxyrepository-adoption-handoff.md`, `stillpending.md` §2, HistorianGateway `pending.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.v1` proto / any client.
|
||||
- The session-resilience epic and other `stillpending.md` items.
|
||||
Reference in New Issue
Block a user