diff --git a/A2-galaxyrepository-adoption-handoff.md b/A2-galaxyrepository-adoption-handoff.md new file mode 100644 index 0000000..2df7b77 --- /dev/null +++ b/A2-galaxyrepository-adoption-handoff.md @@ -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 + + ``` + under the `dohertj2-gitea` ``. (Easy to miss — restore will fail without it.) + +2. **Server `.csproj`** — add: + ```xml + + ``` + (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. diff --git a/docs/plans/2026-06-25-galaxyrepository-upstream-gaps-design.md b/docs/plans/2026-06-25-galaxyrepository-upstream-gaps-design.md new file mode 100644 index 0000000..f7b11bf --- /dev/null +++ b/docs/plans/2026-06-25-galaxyrepository-upstream-gaps-design.md @@ -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>` — `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? 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` `` `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 `` under the `dohertj2-gitea` `packageSourceMapping` source (restore fails silently otherwise). +2. **Server `.csproj`** — ``; 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. diff --git a/stillpending.md b/stillpending.md index ad8307d..06a2d2d 100644 --- a/stillpending.md +++ b/stillpending.md @@ -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`. - 🔵 **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.) +- 🔵 **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. ---