Lands the five concrete code-level follow-ups identified after Phase 7.1:
#1 GalaxyDriver.ReadAsync now works in production. Previously threw
NotSupportedException when no test reader was injected. New path
subscribes through the existing SubscriptionRegistry + EventPump,
waits for the first OnDataChange per item handle (gw pushes the
initial value after SubscribeBulk), then unsubscribes. Tags the gw
rejects up front, or that don't publish before the caller's CT
fires, return Bad-status snapshots in input order so callers still
get one snapshot per requested reference.
#2 ResolveApiKey() routes Gateway.ApiKeySecretRef through three forms:
env:NAME, file:PATH, or literal-string fallback. A future DPAPI arm
slots in here without touching the call site.
#3 GatewayGalaxySubscriber actually honors bufferedUpdateIntervalMs now
(was being silently dropped). Calls SetBufferedUpdateInterval via
the gw's MxCommandKind.SetBufferedUpdateInterval before SubscribeBulk
when the requested interval differs from the cached last-applied
value. Soft-fails on a non-Ok protocol status (the SubscribeBulk
still succeeds at gw cadence).
#4 GalaxyMxAccessOptions.EventPumpChannelCapacity surfaces the bounded-
channel size through DriverConfig JSON, defaulting to 50_000.
#5 Stale doc-comments in HostStatusAggregator and GatewayGalaxySubscriber
describing follow-ups that already shipped.
Tests: +6 (read subscribe-once happy path + rejected-tag fallback;
five resolver scenarios). Total Galaxy driver tests now 180/180 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires MxAccess.PublishingIntervalMs into the gw's SubscribeBulk
bufferedUpdateIntervalMs parameter on both subscribe paths:
- GalaxyDriver.SubscribeAsync — when the caller passes TimeSpan.Zero
(typical for infrastructure callers like the deploy watcher), the
driver substitutes _options.MxAccess.PublishingIntervalMs. When the
caller sets a non-zero interval (the server's UA subscription
publishingInterval), that wins.
- PerPlatformProbeWatcher — new bufferedUpdateIntervalMs ctor parameter
defaulting to 0 (gw default cadence). GalaxyDriver passes
_options.MxAccess.PublishingIntervalMs so probe ScanState changes
publish at the configured rate.
Tests: caller-wins-when-non-zero, fallback-to-config-when-zero on the
driver; default-zero, configured-forwarded, negative-rejected on the
probe watcher.
A session-level SetBufferedUpdateInterval RPC exists in the gw protocol
(MxCommandKind.SetBufferedUpdateInterval) but the .NET client doesn't
expose a typed helper yet — adjusting an existing subscription's
interval is a follow-up. Today's path subscribes once with the right
interval, which covers the common case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Subscription path online. GalaxyDriver implements ISubscribable; subscribes
batches via gw SubscribeBulkAsync, runs a single shared EventPump consumer
of StreamEventsAsync, fans out OnDataChange events to every driver
subscription that observes the changed gw item handle.
Files:
- Runtime/GalaxySubscriptionHandle.cs — record implementing ISubscriptionHandle.
- Runtime/SubscriptionRegistry.cs — bookkeeping with forward (subscriptionId
→ bindings) and reverse (itemHandle → list of subscriptionIds) maps. The
reverse map is the fan-out index so a single OnDataChange dispatches to
every subscription that observes the changed handle.
- Runtime/IGalaxySubscriber.cs — driver-side seam: SubscribeBulk +
UnsubscribeBulk + StreamEventsAsync. Production wraps GalaxyMxSession;
tests substitute a fake driving synthetic MxEvents.
- Runtime/GatewayGalaxySubscriber.cs — production. Forwards to
MxGatewaySession; bufferedUpdateIntervalMs is captured for now and
becomes a SetBufferedUpdateInterval call once gw issue #102 / gw-9 lands
(PR 6.3 picks this up).
- Runtime/EventPump.cs — long-running background consumer of
StreamEventsAsync. Decodes MxValue + maps quality byte/MxStatusProxy via
StatusCodeMap. Fan-out per subscriber resolves through the registry; bad
handler exceptions are caught + logged, never break the dispatch loop.
Filters out non-OnDataChange families (write-complete and operation-
complete come back via InvokeAsync's reply path, not the event stream).
GalaxyDriver:
- Adds ISubscribable. SubscribeAsync allocates a subscription id,
SubscribeBulks, builds the binding list (failed gw entries get
ItemHandle=0 + a per-tag warn log), registers, and returns the handle.
EventPump is started lazily on first subscribe; one pump per driver
shared across all subscriptions.
- UnsubscribeAsync removes from the registry first (so stale events are
filtered immediately) then calls UnsubscribeBulk best-effort. Foreign
handles throw ArgumentException.
- ReadAsync NotSupportedException message updated: PR 4.4 no longer the
pointer (deferred to a small follow-up that wraps the pump as a
one-shot reader).
- Dispose tears down the pump first, then the repository client, then
clears state.
- Internal ctor extended with optional subscriber parameter.
Tests (15 new, 109 Galaxy total):
- SubscriptionRegistryTests: monotonic id allocation, single+multi
subscription fan-out, failed-handle exclusion, removal isolation, count
invariants.
- GalaxyDriverSubscribeTests: handle allocation + value-change dispatch,
multi-subscription fan-out, failed-tag silence, unsubscribe drops gw
handle and stops dispatch, foreign handle throws, no-subscriber throws,
empty-tag-list returns handle without calling gw.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>