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,174 @@
|
|||||||
|
# A2 — Adopt the shared `ZB.MOM.WW.GalaxyRepository` library
|
||||||
|
|
||||||
|
> Handoff note. Written 2026-06-25 from the HistorianGateway side, where the shared lib is
|
||||||
|
> already consumed in production. This is the mxaccessgw half of the cross-repo
|
||||||
|
> "Galaxy-browse normalization" follow-on (HistorianGateway `pending.md` A2 /
|
||||||
|
> scadaproj component-normalization).
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Replace mxaccessgw's **inline** Galaxy-browse code (`src/ZB.MOM.WW.MxGateway.Server/Galaxy/**`
|
||||||
|
plus `Grpc/GalaxyRepositoryGrpcService.cs` + `Grpc/GalaxyProtoMapper.cs`) with a
|
||||||
|
`PackageReference` to the shared **`ZB.MOM.WW.GalaxyRepository`** library, so there is **one**
|
||||||
|
Galaxy-browse implementation shared by both sidecars instead of two copies that can drift.
|
||||||
|
|
||||||
|
The wire contract is **unchanged** — both repos already serve the same `galaxy_repository.v1`
|
||||||
|
proto — so **no client change is required**. This is an internal refactor.
|
||||||
|
|
||||||
|
## Why this is safe to do now
|
||||||
|
|
||||||
|
The shared lib was **extracted from this very code**: the file names under
|
||||||
|
`src/ZB.MOM.WW.MxGateway.Server/Galaxy/**` map ~1:1 onto
|
||||||
|
`scadaproj/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/**`. HistorianGateway
|
||||||
|
consumes it (`PackageReference @ 0.1.0`) and runs it live against `wonder-sql-vd03`. So the SQL
|
||||||
|
provider, hierarchy cache, deploy notifier, snapshot store, background refresh service, projectors,
|
||||||
|
glob matcher, tag lookup, and the gRPC service are **already proven** in the package — the work here
|
||||||
|
is wiring + reconciling the mxaccessgw-only pieces, not reimplementing browse.
|
||||||
|
|
||||||
|
## The clean part — 1:1 swap
|
||||||
|
|
||||||
|
These inline files are superseded by the package (delete on adoption; rebind references to namespace
|
||||||
|
`ZB.MOM.WW.GalaxyRepository`):
|
||||||
|
|
||||||
|
| mxaccessgw inline (`…Server/Galaxy/`) | Shared lib equivalent |
|
||||||
|
|---|---|
|
||||||
|
| `GalaxyRepository.cs` / `IGalaxyRepository.cs` | same |
|
||||||
|
| `GalaxyHierarchyCache.cs` / `IGalaxyHierarchyCache.cs` / `GalaxyHierarchyCacheEntry.cs` | same |
|
||||||
|
| `GalaxyHierarchyRefreshService.cs` | same (registered as `HostedService` by the lib's DI ext) |
|
||||||
|
| `GalaxyDeployNotifier.cs` / `IGalaxyDeployNotifier.cs` / `GalaxyDeployEventInfo.cs` | same |
|
||||||
|
| `GalaxyHierarchySnapshot*.cs` / `IGalaxyHierarchySnapshotStore.cs` | same |
|
||||||
|
| `GalaxyHierarchyProjector.cs` / `GalaxyBrowseProjector.cs` / `GalaxyHierarchyIndex.cs` | same |
|
||||||
|
| `GalaxyObjectView.cs` / `GalaxyHierarchyRow.cs` / `GalaxyAttributeRow.cs` | same |
|
||||||
|
| `GalaxyBrowseChildrenResult.cs` / `GalaxyHierarchyQueryResult.cs` | same |
|
||||||
|
| `GalaxyTagLookup.cs` / `GalaxyGlobMatcher.cs` / `GalaxyCacheStatus.cs` | same |
|
||||||
|
| `GalaxyRepositoryOptions.cs` | `GalaxyRepositoryOptions` (lib ships **no validator** — see below) |
|
||||||
|
| `Grpc/GalaxyRepositoryGrpcService.cs` / `Grpc/GalaxyProtoMapper.cs` | lib bundles its own `Grpc/GalaxyRepositoryGrpcService.cs` + `GalaxyProtoMapper.cs`, mapped by `MapZbGalaxyRepository()` — **but verify authz parity first, see ⚠️ below** |
|
||||||
|
| `Galaxy/GalaxyRepositoryServiceCollectionExtensions.cs` | replaced by the lib's `AddZbGalaxyRepository` / `MapZbGalaxyRepository` |
|
||||||
|
|
||||||
|
## ⚠️ The hard part — mxaccessgw-only consumers the shared lib does NOT cover
|
||||||
|
|
||||||
|
HistorianGateway was a greenfield consumer (browse + dashboard tree only). mxaccessgw has **extra
|
||||||
|
consumers of the Galaxy cache/hierarchy that the shared lib was not extracted with.** These are the
|
||||||
|
real work and the real risk:
|
||||||
|
|
||||||
|
1. **Alarm watch-list / alarm-attribute discovery — capability GAP in the lib.**
|
||||||
|
- mxaccessgw has `Galaxy/GalaxyAlarmAttributeRow.cs` and `GalaxyRepository.GetAlarmAttributesAsync()`,
|
||||||
|
consumed by `Alarms/AlarmWatchListResolver.cs` (+ `IAlarmWatchListResolver`, `GatewayAlarmMonitor`).
|
||||||
|
- The shared lib has **`GalaxyAttributeRow` + `GetAttributesAsync()` only** — there is **no**
|
||||||
|
`GalaxyAlarmAttributeRow` / `GetAlarmAttributesAsync` in `0.1.0`.
|
||||||
|
- **Decision needed:** either (a) push the alarm-attributes SQL projection **upstream** into the
|
||||||
|
shared lib (a `0.2.0` additive feature — preferred, keeps it shared) and re-consume, or
|
||||||
|
(b) keep `AlarmWatchListResolver` + the alarm-attributes query **inline** in mxaccessgw, layered
|
||||||
|
**on top of** the shared `IGalaxyRepository`/`IGalaxyHierarchyCache`. (a) is the spirit of A2.
|
||||||
|
|
||||||
|
2. **Dashboard consumers** — `Dashboard/DashboardGalaxyProjector.cs`, `DashboardBrowseService.cs`
|
||||||
|
(+ `IDashboardBrowseService`, `DashboardBrowseModel`), `DashboardSnapshotService.cs`,
|
||||||
|
`DashboardGalaxySummary.cs`, and the Razor pages `GalaxyPage.razor` / `DashboardHome.razor` all
|
||||||
|
`using ZB.MOM.WW.MxGateway.Server.Galaxy`. Mechanical rebind to `ZB.MOM.WW.GalaxyRepository`, but
|
||||||
|
confirm every type they touch is `public` in the lib (most are; double-check projector/snapshot
|
||||||
|
types).
|
||||||
|
|
||||||
|
3. **Security / authorization** — `Security/Authorization/ConstraintEnforcer.cs` and
|
||||||
|
`GatewayGrpcScopeResolver.cs` consume Galaxy hierarchy to enforce per-key constraints. Rebind, and
|
||||||
|
see the gRPC-service question below.
|
||||||
|
|
||||||
|
⚠️ **gRPC-service authz parity — VERIFIED 2026-06-25: NOT safe to swap wholesale.** The authorization
|
||||||
|
is **split across two layers**, and the part that matters is **baked into the service body**:
|
||||||
|
|
||||||
|
1. **Authentication + scope gating IS interceptor-based ✅ (safe).** `Security/Authorization/GatewayGrpcAuthorizationInterceptor.cs`
|
||||||
|
authenticates the API key and enforces the required scope; all five Galaxy RPCs map to the `metadata`
|
||||||
|
scope (`GatewayGrpcScopeResolver.cs:23-27` → `MetadataRead`). The interceptor also pushes the identity
|
||||||
|
into an ambient `IGatewayRequestIdentityAccessor` for the call. This mirrors HistorianGateway and is
|
||||||
|
shared-lib compatible.
|
||||||
|
2. **Per-key browse-subtree CONSTRAINT FILTERING is baked into the service body ❌ (the blocker).**
|
||||||
|
mxaccessgw's `Grpc/GalaxyRepositoryGrpcService.cs` injects `IGatewayRequestIdentityAccessor` and
|
||||||
|
`ResolveBrowseSubtrees()` (`:287-291`) reads `identityAccessor.Current.EffectiveConstraints.BrowseSubtrees`,
|
||||||
|
threading those globs into the projectors for `DiscoverHierarchy` (`:78`), `BrowseChildren` (`:138`),
|
||||||
|
and the `WatchDeployEvents` count scoping (`:180,196,230-237`) — i.e. it restricts **which Galaxy
|
||||||
|
objects each API key can see**. The shared lib's service injects no identity accessor and passes
|
||||||
|
`browseSubtreeGlobs: null` everywhere; its own XML doc states it *"applies no per-identity
|
||||||
|
browse-subtree filtering … Authorization (including any subtree scoping) is the responsibility of the
|
||||||
|
hosting gateway's interceptor layer."*
|
||||||
|
|
||||||
|
**Severity — silent per-key data exposure.** Glob semantics (`GalaxyHierarchyProjector.cs:225-227`):
|
||||||
|
`null`/empty → match everything; non-empty → restrict. So keys with **no** browse constraint behave
|
||||||
|
identically in both services (no regression), but a key **with** a `BrowseSubtrees` constraint would, on
|
||||||
|
the shared service, receive the **entire** Galaxy hierarchy — a `metadata`-scoped key silently bypassing
|
||||||
|
its restriction. The boundary is locked by tests that would fail against the shared service:
|
||||||
|
`Tests/Gateway/Grpc/GalaxyRepositoryGrpcServiceTests.cs` → `BrowseChildren_BrowseSubtreesConstraint_FiltersChildren`
|
||||||
|
(`:437`) and `DiscoverHierarchy_WithSubtreeRootAndDepth_FiltersDescendants` (`:90`).
|
||||||
|
|
||||||
|
**Recommendation.** Do **not** call `MapZbGalaxyRepository()` directly. The shared projectors already
|
||||||
|
accept a `browseSubtreeGlobs` param, so either fix is small plumbing:
|
||||||
|
- **Preferred (spirit of A2 — push upstream):** give the shared `GalaxyRepositoryGrpcService` an optional
|
||||||
|
browse-subtree-constraint provider (e.g. `IBrowseSubtreeConstraintProvider`/delegate returning globs —
|
||||||
|
`null` in HistorianGateway, identity-derived in mxaccessgw). One shared service, both behaviors; bundle
|
||||||
|
with the same `0.2.0` bump as the alarm-attributes gap above.
|
||||||
|
- **Fallback (isolate to mxaccessgw):** keep a thin mxaccessgw service that resolves subtrees from
|
||||||
|
identity and calls the shared (public) projectors, and map **that** instead of `MapZbGalaxyRepository()`.
|
||||||
|
|
||||||
|
## Concrete wiring steps (template from the HistorianGateway side)
|
||||||
|
|
||||||
|
1. **`nuget.config`** — mxaccessgw's `packageSourceMapping` for the `dohertj2-gitea` source currently
|
||||||
|
lists `ZB.MOM.WW.*` up to `Theme` but **NOT** GalaxyRepository. Add:
|
||||||
|
```xml
|
||||||
|
<package pattern="ZB.MOM.WW.GalaxyRepository" />
|
||||||
|
```
|
||||||
|
under the `dohertj2-gitea` `<packageSource>`. (Easy to miss — restore will fail without it.)
|
||||||
|
|
||||||
|
2. **Server `.csproj`** — add:
|
||||||
|
```xml
|
||||||
|
<PackageReference Include="ZB.MOM.WW.GalaxyRepository" Version="0.1.0" />
|
||||||
|
```
|
||||||
|
(or whatever version ships the alarm-attributes add, if you go route 1(a) above.)
|
||||||
|
|
||||||
|
3. **DI + endpoint wiring** (in mxaccessgw's host composition — `GatewayApplication.cs` / its
|
||||||
|
`Program`), mirroring HistorianGateway's `Program.cs`:
|
||||||
|
```csharp
|
||||||
|
using ZB.MOM.WW.GalaxyRepository;
|
||||||
|
using ZB.MOM.WW.GalaxyRepository.DependencyInjection;
|
||||||
|
|
||||||
|
// service registration — bind from mxaccessgw's section path (NOT top-level "Galaxy"):
|
||||||
|
builder.Services.AddZbGalaxyRepository(builder.Configuration, "MxGateway:Galaxy"); // confirm the exact section path mxaccessgw uses today
|
||||||
|
|
||||||
|
// endpoint pipeline (after AddGrpc):
|
||||||
|
app.MapZbGalaxyRepository();
|
||||||
|
```
|
||||||
|
Delete mxaccessgw's own `Galaxy/GalaxyRepositoryServiceCollectionExtensions.cs` registrations.
|
||||||
|
|
||||||
|
4. **Option validation** — the shared lib **binds only, ships no validator** (deliberate). mxaccessgw
|
||||||
|
already validates Galaxy options via `Configuration/GatewayOptionsValidator.cs` — **keep that**; it
|
||||||
|
stays the owner of fail-fast validation, exactly as HistorianGateway's `ConfigPreflight` does.
|
||||||
|
|
||||||
|
5. **Health check** — keep mxaccessgw's existing Galaxy-SQL readiness check; read the connection
|
||||||
|
string from the same `MxGateway:Galaxy` section the lib binds (HistorianGateway does this with a raw
|
||||||
|
`SELECT 1` `SqlConnectionHealthCheck` — see `Program.cs:196`).
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
mxaccessgw has a large `Tests/Galaxy/**` suite that **duplicates the shared lib's own tests**
|
||||||
|
(`GalaxyHierarchyCacheTests`, `GalaxyHierarchyProjectorTests`, `GalaxyHierarchySnapshotStoreTests`,
|
||||||
|
`GalaxyProtoMapperTests`, `GalaxyHierarchyIndexTests`). On adoption these are covered upstream and can
|
||||||
|
be **deleted**. **Keep** the mxaccessgw-specific ones that exercise behavior the lib doesn't own:
|
||||||
|
`GalaxyAlarmAttributeMappingTests`, `GalaxyFilterInputSafetyTests`, `GalaxyRepositoryGrpcServiceTests`
|
||||||
|
(unless their subjects move upstream too), and the live `IntegrationTests/Galaxy/**`.
|
||||||
|
|
||||||
|
## Suggested order
|
||||||
|
|
||||||
|
1. ~~Verify the **gRPC-service authz parity** question~~ **DONE (2026-06-25):** wholesale swap is unsafe —
|
||||||
|
per-key browse-subtree filtering is baked into the service body. The service must keep an mxaccessgw
|
||||||
|
subtree-scoping hook (push the provider upstream, or wrap). See the ⚠️ block above.
|
||||||
|
2. Decide alarm-attributes: **upstream into the lib (`0.2.0`)** vs keep inline on top of shared interfaces.
|
||||||
|
3. If upstreaming: do that in `scadaproj/ZB.MOM.WW.GalaxyRepository` first, publish, bump the version.
|
||||||
|
4. `nuget.config` + `csproj` + DI/endpoint wiring; delete the superseded inline files; rebind namespaces
|
||||||
|
in dashboard/alarms/security consumers.
|
||||||
|
5. Delete duplicated tests; build zero-warning; run the suite; live-validate browse + alarm watch-list.
|
||||||
|
6. Propagate to the scadaproj umbrella index + HistorianGateway's `pending.md` A2 (mark adopted).
|
||||||
|
|
||||||
|
## Reference pointers
|
||||||
|
|
||||||
|
- **Working consumer (copy this):** `~/Desktop/HistorianGateway/src/ZB.MOM.WW.HistorianGateway.Server/Program.cs`
|
||||||
|
(`AddZbGalaxyRepository` @ ~line 174, `MapZbGalaxyRepository` @ ~line 314) + its `nuget.config` + Server `.csproj`.
|
||||||
|
- **Shared lib source:** `~/Desktop/scadaproj/ZB.MOM.WW.GalaxyRepository/` (DI ext at
|
||||||
|
`src/ZB.MOM.WW.GalaxyRepository/DependencyInjection/GalaxyRepositoryServiceCollectionExtensions.cs`).
|
||||||
|
- **Tracked in:** HistorianGateway `pending.md` §A2 + `CLAUDE.md` → Known Follow-Ons; scadaproj component normalization.
|
||||||
@@ -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.
|
||||||
@@ -63,6 +63,7 @@ These are documented, deliberate, and mostly enforced. Listed so the deferred su
|
|||||||
- 🔵 **No server-side / streaming browse search** — `docs/plans/2026-05-28-lazy-browse-design.md:208`.
|
- 🔵 **No server-side / streaming browse search** — `docs/plans/2026-05-28-lazy-browse-design.md:208`.
|
||||||
- 🔵 **Alarm command surface is ack + query only** — no Clear/Disable/Enable/Silence/Shelve/Inhibit; matches the MXAccess alarm-client set. `Worker/MxAccess/AlarmCommandHandler.cs`, shelve/suppress out of scope per `docs/AlarmClientDiscovery.md:60-66`.
|
- 🔵 **Alarm command surface is ack + query only** — no Clear/Disable/Enable/Silence/Shelve/Inhibit; matches the MXAccess alarm-client set. `Worker/MxAccess/AlarmCommandHandler.cs`, shelve/suppress out of scope per `docs/AlarmClientDiscovery.md:60-66`.
|
||||||
- 🟡 **Dashboard EventsHub has no per-session ACL — still true on `main`, planned (epic Phase 4, not started).** Any authenticated dashboard user may still subscribe to any session group (`Dashboard/Hubs/EventsHub.cs` `TODO(per-session-acl)`). The enabling foundation (session `OwnerKeyId`) already merged in epic Phase 1; epic Phase 4 (Tasks 16–19) adds the gRPC session-owner gate, a session tag + group-to-tag config, and EventsHub per-session ACL with a hub-token tag claim. `docs/plans/2026-06-15-session-resilience.md` Phase 4. (See also §8.)
|
- 🟡 **Dashboard EventsHub has no per-session ACL — still true on `main`, planned (epic Phase 4, not started).** Any authenticated dashboard user may still subscribe to any session group (`Dashboard/Hubs/EventsHub.cs` `TODO(per-session-acl)`). The enabling foundation (session `OwnerKeyId`) already merged in epic Phase 1; epic Phase 4 (Tasks 16–19) adds the gRPC session-owner gate, a session tag + group-to-tag config, and EventsHub per-session ACL with a hub-token tag claim. `docs/plans/2026-06-15-session-resilience.md` Phase 4. (See also §8.)
|
||||||
|
- 🔵 **Adopt the shared `ZB.MOM.WW.GalaxyRepository` library (cross-repo normalization) — not started.** The inline Galaxy-browse code under `src/ZB.MOM.WW.MxGateway.Server/Galaxy/**` (+ `Grpc/GalaxyRepositoryGrpcService.cs`/`GalaxyProtoMapper.cs`) is the source the shared lib was extracted from; HistorianGateway already consumes it as a `PackageReference @ 0.1.0`. Swapping to the package gives one Galaxy-browse implementation across both sidecars (same `galaxy_repository.v1` wire contract — no client change). Non-trivial: mxaccessgw has extra consumers the lib doesn't cover (alarm watch-list / `GetAlarmAttributesAsync`, dashboard projectors, constraint-authz). The gRPC-service authz-parity gate is **verified (2026-06-25): a wholesale swap to `MapZbGalaxyRepository()` is unsafe** — per-key browse-subtree filtering is baked into mxaccessgw's service body (`Grpc/GalaxyRepositoryGrpcService.cs`), so the service must keep a subtree-scoping hook (push the provider upstream, or wrap), not just rely on the interceptor. Full handoff: **`A2-galaxyrepository-adoption-handoff.md`** (repo root). Tracked cross-repo in HistorianGateway `pending.md` §A2 + scadaproj component normalization.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user