522 lines
24 KiB
Markdown
522 lines
24 KiB
Markdown
# Cluster 08 — Galaxy Repository
|
||
|
||
Audited doc: `docs/GalaxyRepository.md`
|
||
Verified against: `src/ZB.MOM.WW.MxGateway.Server/Galaxy/**`, `src/ZB.MOM.WW.MxGateway.Contracts/Protos/galaxy_repository.proto`
|
||
Date: 2026-06-03
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 3–4
|
||
CLAIM: The SQL Server database is named `ZB`.
|
||
CLAIM_TYPE: config-key
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryOptions.cs:17 (`DefaultConnectionString = "Server=localhost;Database=ZB;..."`)
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 3–4
|
||
CLAIM: The database is a SQL Server database.
|
||
CLAIM_TYPE: config-key
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:1 (`using Microsoft.Data.SqlClient;`); GalaxyRepositoryOptions.cs:17
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 30–31
|
||
CLAIM: The service is defined in `src/ZB.MOM.WW.MxGateway.Contracts/Protos/galaxy_repository.proto` under package `galaxy_repository.v1`.
|
||
CLAIM_TYPE: path
|
||
VERDICT: accurate
|
||
EVIDENCE: galaxy_repository.proto:3 (`package galaxy_repository.v1;`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 35–39
|
||
CLAIM: `TestConnection` returns `{ ok: bool }` after a `SELECT 1`. Does not throw on SQL failure — returns `ok = false`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:20–32 (catches `SqlException` and `InvalidOperationException`, returns false); galaxy_repository.proto:43–45 (`TestConnectionReply { bool ok = 1; }`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 36
|
||
CLAIM: `GetLastDeployTime` returns the cached `galaxy.time_of_last_deploy`. Served from the shared hierarchy cache; refreshed in the background.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: wrong
|
||
EVIDENCE: GalaxyRepositoryGrpcService.cs:42–62 — `GetLastDeployTime` calls `WaitForCacheBootstrap` then reads `cache.Current`, not `repository` directly. The underlying SQL is `SELECT time_of_last_deploy FROM galaxy` (GalaxyRepository.cs:40) but it is served from cache, not direct SQL. The doc correctly says "served from cache" in the inline column. However the inline description says "Served from the shared hierarchy cache; refreshed in the background" which is accurate for the RPC handler — but the SQL column itself (`galaxy.time_of_last_deploy`) is an internal SQL column name, not a table.column phrasing. No actual error; accurate.
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 38–39
|
||
CLAIM: `WatchDeployEvents` is server-streaming. The server emits the current state immediately on subscribe.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: galaxy_repository.proto:33 (`rpc WatchDeployEvents ... returns (stream DeployEvent)`); GalaxyDeployNotifier.cs:58–63 (bootstrap emit on subscribe)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 39
|
||
CLAIM: `BrowseChildren` returns the direct children of one parent object (or root objects when `parent` is unset). Includes a per-child `has_children` hint so UIs can draw expand triangles without an extra round trip. Served from cache.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: galaxy_repository.proto:175–190 (`BrowseChildrenReply` with `child_has_children` repeated bool); GalaxyRepositoryGrpcService.cs:112–168
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 42–43
|
||
CLAIM: The server defaults omitted page size to 1000 objects and caps every page at 5000 objects (for `DiscoverHierarchy`).
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryGrpcService.cs:27–28 (`DefaultDiscoverPageSize = 1000`, `MaxDiscoverPageSize = 5000`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 83–86
|
||
CLAIM: `BrowseChildren` default page size is 500; the server caps any requested size at 5000. Page tokens encode `(cache_sequence, parent_id, filter_signature, offset)`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: wrong
|
||
EVIDENCE: GalaxyRepositoryGrpcService.cs:29 (`DefaultBrowsePageSize = 500`) — default is accurate. Cap of 5000 is accurate (comment "MaxBrowsePageSize reuses MaxDiscoverPageSize (5000)"). However the token encoding claim is inaccurate: the actual token format is `sequence:filterSignature:offset` (GalaxyRepositoryGrpcService.cs:295–302, `FormatPageToken`). `parent_id` is embedded inside `filterSignature` as a component (GalaxyBrowseProjector.cs:266 `builder.Append("parent=").Append(parentId...)`) — it is NOT a separate named field in the token. Describing it as `(cache_sequence, parent_id, filter_signature, offset)` implies four independent fields; the wire encoding has three fields with parent_id folded into the signature hash.
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: medium
|
||
PROPOSED_FIX: Change "Page tokens encode `(cache_sequence, parent_id, filter_signature, offset)`" to "Page tokens encode `sequence:filterSignature:offset`; `parent_id` is incorporated into `filterSignature` along with the other filter parameters."
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 97–98
|
||
CLAIM: Missing `metadata:read` scope returns `PermissionDenied`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GatewayGrpcScopeResolver.cs:23–27 (all five Galaxy request types map to `GatewayScopes.MetadataRead`); GatewayScopes.cs:11
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 118–119
|
||
CLAIM: `GalaxyHierarchyRefreshService` ticks every `MxGateway:Galaxy:DashboardRefreshIntervalSeconds` seconds (default 30).
|
||
CLAIM_TYPE: config-key
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryOptions.cs:28–29 (`DashboardRefreshIntervalSeconds { get; init; } = 30`); GalaxyHierarchyRefreshService.cs:18 (`TimeSpan.FromSeconds(Math.Max(1, options.Value.DashboardRefreshIntervalSeconds))`)
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 120
|
||
CLAIM: Each tick queries the cheap `SELECT time_of_last_deploy FROM galaxy` first.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:40 (`"SELECT time_of_last_deploy FROM galaxy"`); GalaxyHierarchyCache.cs:117 (`GetLastDeployTimeAsync` called first before deciding whether to run heavy queries)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 150–152
|
||
CLAIM: The snapshot file is written atomically — a temp file plus rename — so a crash mid-write cannot corrupt the snapshot.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyHierarchySnapshotStore.cs:74–81 (writes to `_path + ".tmp"` then `File.Move(..., overwrite: true)`)
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 178–179
|
||
CLAIM: `GalaxyDeployNotifier` maintains a private bounded channel per subscriber. The bound is 16 events with `DropOldest`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyDeployNotifier.cs:18 (`SubscriberQueueCapacity = 16`); GalaxyDeployNotifier.cs:49–53 (`BoundedChannelOptions` with `FullMode = BoundedChannelFullMode.DropOldest`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 386–387
|
||
CLAIM: Default connection string is `Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;`
|
||
CLAIM_TYPE: config-key
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryOptions.cs:17 (exact match)
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 387
|
||
CLAIM: `MxGateway:Galaxy:CommandTimeoutSeconds` default is `60`. Applies to all three RPCs.
|
||
CLAIM_TYPE: config-key
|
||
VERDICT: wrong
|
||
EVIDENCE: GalaxyRepositoryOptions.cs:22 (`CommandTimeoutSeconds { get; init; } = 60`) — default is accurate. However "Applies to all three RPCs" is stale: there are five RPCs (`TestConnection`, `GetLastDeployTime`, `DiscoverHierarchy`, `WatchDeployEvents`, `BrowseChildren`), not three. `CommandTimeoutSeconds` applies to the SQL commands in `GalaxyRepository.cs` which backs `TestConnection`, `GetLastDeployTime`, `GetHierarchyAsync`, and `GetAttributesAsync`. The doc says "all three RPCs" presumably counting only the original three before `BrowseChildren` was added.
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: medium
|
||
PROPOSED_FIX: Change "Applies to all three RPCs" to "Applies to all SQL commands issued by the repository (used by `TestConnection`, `GetLastDeployTime`, and the hierarchy/attributes queries backing `DiscoverHierarchy` and `BrowseChildren`)."
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 388–389
|
||
CLAIM: `MxGateway:Galaxy:PersistSnapshot` default is `true`.
|
||
CLAIM_TYPE: config-key
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryOptions.cs:40 (`PersistSnapshot { get; init; } = true`)
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 389–390
|
||
CLAIM: `MxGateway:Galaxy:SnapshotCachePath` default is `C:\ProgramData\MxGateway\galaxy-snapshot.json`.
|
||
CLAIM_TYPE: config-key
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryOptions.cs:32–33 (`DefaultSnapshotCachePath = @"C:\ProgramData\MxGateway\galaxy-snapshot.json"`)
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 403–404
|
||
CLAIM: "All four Galaxy RPCs (including `WatchDeployEvents`) require the `metadata:read` API-key scope."
|
||
CLAIM_TYPE: rpc/proto
|
||
VERDICT: wrong
|
||
EVIDENCE: GatewayGrpcScopeResolver.cs:23–27 — all **five** Galaxy RPCs require `metadata:read`: `TestConnectionRequest`, `GetLastDeployTimeRequest`, `DiscoverHierarchyRequest`, `WatchDeployEventsRequest`, and `BrowseChildrenRequest`. The service has five RPCs (galaxy_repository.proto:21–39), not four. `BrowseChildren` was added after the original four but the authorization section was not updated.
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: high
|
||
PROPOSED_FIX: Change "All four Galaxy RPCs" to "All five Galaxy RPCs" (or explicitly list all five: `TestConnection`, `GetLastDeployTime`, `DiscoverHierarchy`, `WatchDeployEvents`, `BrowseChildren`).
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 378
|
||
CLAIM: "`GalaxyRepositoryGrpcService` (`src/ZB.MOM.WW.MxGateway.Server/Grpc/GalaxyRepositoryGrpcService.cs`) implements the five RPCs."
|
||
CLAIM_TYPE: path
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryGrpcService.cs (file exists at that path); implements all five overrides: TestConnection, GetLastDeployTime, DiscoverHierarchy, WatchDeployEvents, BrowseChildren
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 327
|
||
CLAIM: Architecture diagram shows `DiscoverHierarchy, GetLastDeployTime, BrowseChildren -> IGalaxyHierarchyCache.Current` (WatchDeployEvents -> IGalaxyDeployNotifier, TestConnection -> GalaxyRepository direct SQL).
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryGrpcService.cs:33–39 (TestConnection → repository.TestConnectionAsync), :42–62 (GetLastDeployTime → cache.Current), :64–110 (DiscoverHierarchy → cache.Current), :112–168 (BrowseChildren → cache.Current), :171–200 (WatchDeployEvents → notifier.SubscribeAsync)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 346–350
|
||
CLAIM: "`GalaxyRepository` (`src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyRepository.cs`) holds the SQL. Both `HierarchySql` and `AttributesSql` walk template-derivation and package-derivation chains via recursive CTEs."
|
||
CLAIM_TYPE: path
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:117–164 (`HierarchySql` with `template_chain` CTE); GalaxyRepository.cs:176–251 (`AttributesSql` with `deployed_package_chain` CTE)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 347–348
|
||
CLAIM: "`HierarchySql` still matches the OtOpcUa original; `AttributesSql` does not — it additionally enumerates built-in primitive attributes."
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:9–14 (doc comment confirming this); GalaxyRepository.cs:166–175 (comment on AttributesSql confirming divergence)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 269–270
|
||
CLAIM: Configured (dynamic) attributes are stored in the Galaxy `dynamic_attribute` table.
|
||
CLAIM_TYPE: term
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:196 (`INNER JOIN dynamic_attribute da ON da.package_id = dpc.package_id`)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 272–273
|
||
CLAIM: Built-in attributes are stored in `attribute_definition` and reached through `primitive_instance`.
|
||
CLAIM_TYPE: term
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:214–218 (`INNER JOIN primitive_instance pi ON pi.package_id = dpc.package_id` / `INNER JOIN attribute_definition ad ON ad.primitive_definition_id = pi.primitive_definition_id`)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 283–284
|
||
CLAIM: The configured-attribute category allow-list is `mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:203 (`AND da.mx_attribute_category IN (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 24)`)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 283–285
|
||
CLAIM: No category filter applies to built-in rows (`attribute_definition`); only the `_`-prefixed-name and `.Description` exclusions apply.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:221–223 (`AND ad.attribute_name NOT LIKE '[_]%'` and `NOT LIKE '%.Description'` — no `mx_attribute_category` filter for the built-in branch)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 285–287
|
||
CLAIM: "`is_historized` / `is_alarm` are always `false` for built-in rows."
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:236–248 — both `is_historized` and `is_alarm` use `CASE WHEN r.src_pri = 0 AND EXISTS (...)` — built-in rows have `src_pri = 1` so both expressions evaluate to 0 (false).
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 288–290
|
||
CLAIM: "When a configured attribute and a built-in attribute resolve to the same reference, the configured attribute wins."
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepository.cs:225–228 — `ROW_NUMBER() OVER (PARTITION BY c.gobject_id, c.attribute_name ORDER BY c.src_pri, c.depth)` — `src_pri = 0` for configured rows, `src_pri = 1` for built-ins, so configured attributes are ranked first.
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 420–422
|
||
CLAIM: Dashboard `/dashboard/galaxy` page with object-category and top-template breakdowns.
|
||
CLAIM_TYPE: path
|
||
VERDICT: wrong
|
||
EVIDENCE: GalaxyPage.razor:1 — the page route is `@page "/galaxy"`, not `/dashboard/galaxy`. The Blazor app is mounted without a `/dashboard` prefix (DashboardEndpointRouteBuilderExtensions.cs:86, `MapRazorComponents<App>()`). The Galaxy page is at `/galaxy`, not `/dashboard/galaxy`. The home page at `@page "/"` is the dashboard overview, not at `/dashboard`.
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: high
|
||
PROPOSED_FIX: Change `/dashboard/galaxy` to `/galaxy` and `/dashboard` to `/` throughout the Dashboard Surface section (lines 419–421). The Blazor router has no `/dashboard` prefix.
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 419–420
|
||
CLAIM: "An overview card on `/dashboard` showing connectivity status..."
|
||
CLAIM_TYPE: path
|
||
VERDICT: wrong
|
||
EVIDENCE: DashboardHome.razor:1 — `@page "/"`. The home/overview page is at `/`, not `/dashboard`.
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: high
|
||
PROPOSED_FIX: Change `/dashboard` to `/` in the Dashboard Surface section.
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 369–375
|
||
CLAIM: "`GalaxyBrowseProjector` (`src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyBrowseProjector.cs`) projects one level of children out of an immutable cache entry. Memoizes the filtered child list per cache-entry instance so repeated paging is an O(pageSize) slice rather than an O(siblings) filter scan. The memo is keyed on the cache entry reference, so a new entry from the background refresh makes the stale memo unreachable and it is collected with it. `DashboardBrowseService` wraps this projector to drive the dashboard's lazy-expand tree."
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyBrowseProjector.cs:20–22 (ConditionalWeakTable keyed on `GalaxyHierarchyCacheEntry`); DashboardBrowseService.cs:55 (`GalaxyBrowseProjector.ProjectChildren` called inside `DashboardBrowseService`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 110–111
|
||
CLAIM: "`IGalaxyHierarchyCache` (`src/ZB.MOM.WW.MxGateway.Server/Galaxy/GalaxyHierarchyCache.cs`) — every `DiscoverHierarchy` and `GetLastDeployTime` request reads from this cache."
|
||
CLAIM_TYPE: path
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyHierarchyCache.cs (file at that path); GalaxyRepositoryGrpcService.cs:46–62 (GetLastDeployTime reads from cache); :69–110 (DiscoverHierarchy reads from cache)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 445–447
|
||
CLAIM: "Integration tests live in `src/ZB.MOM.WW.MxGateway.IntegrationTests/Galaxy/GalaxyRepositoryLiveTests.cs`. Set `MXGATEWAY_RUN_LIVE_GALAXY_TESTS=1` (and optionally `MXGATEWAY_LIVE_GALAXY_CONN`) to run them."
|
||
CLAIM_TYPE: path
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyRepositoryLiveTests.cs (file exists at that path); LiveGalaxyRepositoryFactAttribute.cs:9 (`EnableVariableName = "MXGATEWAY_RUN_LIVE_GALAXY_TESTS"`); LiveGalaxyRepositoryFactAttribute.cs:11 (`ConnectionStringVariableName = "MXGATEWAY_LIVE_GALAXY_CONN"`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 365–367
|
||
CLAIM: "`GalaxyProtoMapper` (`src/ZB.MOM.WW.MxGateway.Server/Grpc/GalaxyProtoMapper.cs`) converts row models to proto messages. Used by the cache during refresh to materialize the reply once."
|
||
CLAIM_TYPE: path
|
||
VERDICT: accurate
|
||
EVIDENCE: GalaxyProtoMapper.cs (file at that path); GalaxyHierarchyCache.cs:223 (`BuildObjects` → `GalaxyProtoMapper.MapObject`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 212–261
|
||
CLAIM: The `GalaxyObject`, `GalaxyAttribute`, `DiscoverHierarchyRequest`, and `DiscoverHierarchyReply` message field numbers and types as shown in the "Reply shape" proto block (field numbers 1–12 for `GalaxyAttribute`, 1–12 for `DiscoverHierarchyRequest`, etc.).
|
||
CLAIM_TYPE: rpc/proto
|
||
VERDICT: accurate
|
||
EVIDENCE: galaxy_repository.proto:110–191 (all field numbers and types match the doc's code block)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: 399–400
|
||
CLAIM: Dashboard "displays only non-secret fields: server, database, integrated security, encrypt, and trust-server-certificate. It never displays user id, password, access token, or arbitrary unparsed connection string text."
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: unverifiable
|
||
EVIDENCE: GalaxyPage.razor:129 (`DashboardDisplay.Text(GalaxyConnectionStringDisplay())`); GalaxyPage.razor:193–196 delegates to `DashboardConnectionStringDisplay.GalaxyRepositoryConnectionString`. The actual display logic lives in `DashboardConnectionStringDisplay` which was not found in this audit scope. The behavior is asserted plausibly consistent with "never display user id, password" but the implementation of `DashboardConnectionStringDisplay` was not directly verified.
|
||
CODE_AREA: gr.conn
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only — verify `DashboardConnectionStringDisplay` filters fields as claimed.
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: N/A — not covered in doc
|
||
CLAIM: GAP — `GalaxyHierarchyCache` projects `Status` to `Stale` when `LastSuccessAt` is more than 5 minutes old (regardless of the stored status), via `ProjectStatus` with `StaleThreshold = TimeSpan.FromMinutes(5)`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: gap
|
||
EVIDENCE: GalaxyHierarchyCache.cs:22 (`StaleThreshold = TimeSpan.FromMinutes(5)`); GalaxyHierarchyCache.cs:474–488 (`ProjectStatus` method)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: medium
|
||
PROPOSED_FIX: Add a note under "Hierarchy Cache" that the cache also auto-degrades to `Stale` status when more than 5 minutes have elapsed since the last successful refresh, independent of the stored entry status. This matters for operators diagnosing why a `Healthy` entry flips to `Stale` without a SQL failure.
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: N/A — not covered in doc
|
||
CLAIM: GAP — `WatchDeployEvents` emits a bootstrap event even on a snapshot-restore (from on-disk data), not only from live SQL queries. `GalaxyHierarchyCache.TryRestoreFromDiskAsync` calls `_notifier.Publish` after restoring.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: gap
|
||
EVIDENCE: GalaxyHierarchyCache.cs:315–320 (`_notifier.Publish` called from `TryRestoreFromDiskAsync`)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: Note under "Deploy Notifications" or "On-disk snapshot" that restoring the snapshot also publishes a deploy event so `WatchDeployEvents` subscribers receive a bootstrap event even when SQL is unreachable at startup.
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: N/A — not covered in doc
|
||
CLAIM: GAP — `GalaxyHierarchyRefreshService` runs an initial `RefreshAsync` immediately on startup (before starting the periodic timer), so the first load happens at process start, not after the first tick of `DashboardRefreshIntervalSeconds`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: gap
|
||
EVIDENCE: GalaxyHierarchyRefreshService.cs:22–37 (initial `await cache.RefreshAsync` before `PeriodicTimer` is created)
|
||
CODE_AREA: gr.proto
|
||
SEVERITY: low
|
||
PROPOSED_FIX: Add a note under "Hierarchy Cache" that the first refresh runs immediately at gateway startup and does not wait for the first timer tick.
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: N/A — not covered in doc
|
||
CLAIM: GAP — The `HierarchySql` category filter (`td.category_id IN (1, 3, 4, 10, 11, 13, 17, 24, 26)`) and the specific category IDs mapped to names (WinPlatform=1, AppEngine=3, InTouchViewApp=4, UserDefined=10, FieldReference=11, Area=13, DIObject=17, DDESuiteLinkClient=24, OPCClient=26) are not documented anywhere in `GalaxyRepository.md`.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: gap
|
||
EVIDENCE: GalaxyRepository.cs:161 (HierarchySql WHERE clause with category IDs); GalaxyHierarchyCache.cs:461–472 (`ResolveCategoryName` method mapping each ID to a name)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: medium
|
||
PROPOSED_FIX: Add a table of the filtered category IDs and their names (WinPlatform, AppEngine, InTouchViewApp, etc.) to the doc. Operators need to know which object types are included — an AppEngine that doesn't appear in browse results is hard to diagnose without this list.
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: N/A — not covered in doc
|
||
CLAIM: GAP — The `AttributesSql` uses the `data_type` table to resolve `data_type_name` (`LEFT JOIN data_type dt ON dt.mx_data_type = r.mx_data_type`). The Galaxy table name `data_type` is not mentioned in the doc.
|
||
CLAIM_TYPE: term
|
||
VERDICT: gap
|
||
EVIDENCE: GalaxyRepository.cs:249 (`LEFT JOIN data_type dt ON dt.mx_data_type = r.mx_data_type`)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|
||
|
||
---
|
||
|
||
DOC: docs/GalaxyRepository.md
|
||
LINES: N/A — not covered in doc
|
||
CLAIM: GAP — The `HierarchySql` uses the tables `gobject` and `template_definition`, and maps `parent_gobject_id` using `CASE WHEN g.contained_by_gobject_id = 0 THEN g.area_gobject_id ELSE g.contained_by_gobject_id END`. This parent resolution logic (area_gobject_id fallback) is not mentioned.
|
||
CLAIM_TYPE: behavior-rule
|
||
VERDICT: gap
|
||
EVIDENCE: GalaxyRepository.cs:138–142 (parent_gobject_id CASE expression); tables referenced: `gobject` (line 158), `template_definition` (line 159)
|
||
CODE_AREA: gr.sql
|
||
SEVERITY: low
|
||
PROPOSED_FIX: flag only
|