# 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()`). 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