Commit Graph

1023 Commits

Author SHA1 Message Date
Joseph Doherty 8212b0e550 Merge stream D (dashboard: bounded hub fan-out, alarm reset marker, browse debounce, snapshot config once, health status, per-viewer LRU, circuit retention, doc rewrite) 2026-10-22 06:32:17 -04:00
Joseph Doherty bd7abf4e35 fix(dashboard): re-check hub viewers under lock before starting a pump; document alarm send-timeout tradeoff 2026-10-22 06:32:17 -04:00
Joseph Doherty 2c963b7230 docs(dashboard): describe the pages as shipped and the bounded hub/feed behaviour
Rewrite the Pages 'Show:' lists to what each page actually renders (no
per-session subscriber/pending-command/queue counters, no worker memory/CPU,
no per-family command counters, counters not rates). Document the per-session
EventsHub pump, alarm-hub send bound and AlarmFeedReset marker, configuration
sent once per snapshot-hub connection, health-derived gateway status, Browse
search debounce/index, bounded disposal, 30s circuit retention with
connection-gated polling, per-viewer advise-set share, and AlarmsPage memo.
2026-10-22 06:27:14 -04:00
Joseph Doherty db7833609d fix(dashboard): debounced off-dispatcher Browse search, bounded disposal, circuit-aware polling
- Search: the oninput setter scanned the whole Galaxy synchronously per
  keystroke. It now debounces 150ms, runs on the thread pool with
  cancellation (newer keystroke supersedes), and reads a shared singleton
  DashboardBrowseSearchIndex of pre-lowercased references keyed on the
  Galaxy cache Sequence.
- Disposal: DisposeAsync awaited both loops unbounded and the loops caught
  only OCE. Loops now handle faults per iteration (poll faults surface in
  _readError, deploy-notifier faults in a retrying warning), mutate the tree
  on the dispatcher, drain concurrently with a 5s bound, and the CTSs are
  disposed in finally.
- Circuits: DisconnectedCircuitRetentionPeriod is 30s (was the 3 min
  default) and a scoped DashboardCircuitConnectionTracker (CircuitHandler)
  lets the 2s ReadBulk poll skip ticks while the browser is disconnected.
- Reads pass a per-page viewer id to the live-data per-viewer share.
2026-10-22 06:25:46 -04:00
Joseph Doherty 5d4aefc99c fix(dashboard): per-viewer share of the live-data advise set
The 256-tag LRU was global, so two Browse circuits on different areas evicted
each other's whole sets every poll. Tags now record the viewer that read them
last; each active viewer (read within 30s) owns MaxSubscribedTags/active
(floor 32). Eviction takes other viewers' excess over their share first
(inactive viewers' share is zero), then the reader's own untouched tags, and
never strips a neighbour within its share (soft cap). Single-viewer behaviour
is unchanged. IDashboardLiveDataService gains a viewerId ReadAsync overload.
2026-10-22 06:22:17 -04:00
Joseph Doherty 2a7d6facce perf(dashboard): memoize AlarmsPage filtered rows and area list
FilteredAlarms() ran the full filter+sort twice per render (Showing card and
table) and Areas() once more, including every search keystroke render. Both
now go through DashboardAlarmView, recomputed only when the alarm generation
(bumped per poll) or the filter criteria change.
2026-10-22 06:19:34 -04:00
Joseph Doherty 6733519ae5 fix(dashboard): derive gateway status from health checks; send config once per hub connection
SEC-27: DashboardSnapshot.GatewayStatus was hardcoded Healthy. It is now the
aggregate HealthCheckService status, evaluated once per snapshot tick (2s
bound, zero-wait gate) and cached for GetSnapshot; Unknown before the first
evaluation, on failure, or without a health service.

The startup-static EffectiveGatewayConfiguration is JsonIgnore'd on the
snapshot, so /hubs/snapshot ticks carry only volatile state. The hub sends it
once per connection (ConfigurationUpdated) and serves GetConfiguration().
Snapshot broadcasts are time-bounded (1s) so one slow client cannot delay
ticks. StatusBadge maps Unhealthy to Bad.
2026-10-22 06:18:02 -04:00
Joseph Doherty 3688fa19e6 fix(dashboard): time-bound alarm hub sends and mark each resubscribe
AlarmsHubPublisher awaited every group send unbounded inside the StreamAsync
loop, so one slow client stalled the drain until the monitor dropped the
subscriber and the resubscribe replayed the full snapshot to everyone. Each
broadcast is now bounded (2s) and abandoned on timeout, and every
(re)subscription is preceded by an AlarmFeedReset frame so clients discard
state before the replayed snapshot.
2026-10-22 06:14:53 -04:00
Joseph Doherty f1db002b87 fix(dashboard): bound EventsHub delivery with one drop-oldest pump per session
Publish fired a group SendAsync per event and returned, so a slow remote
viewer accumulated unbounded pending sends plus cloned MxEvents. Hub delivery
now goes through a 256-deep DropOldest channel per hub-watched session with a
single drain task and time-bounded sends. The registry counts hub viewers
separately and raises HubViewersReleased so the pump is torn down with the
last remote viewer; in-process-only sessions start no pump.
2026-10-22 06:13:05 -04:00
Joseph Doherty 81d4cd8644 Merge stream B (bound WorkerClient.ShutdownAsync enqueue by shutdown timeout) 2026-10-22 06:11:17 -04:00
Joseph Doherty b0100e738a fix(worker): bound ShutdownAsync's envelope enqueue by the shutdown timeout
WorkerClient.ShutdownAsync marked the client Closing and then queued the
WorkerShutdown envelope on the caller's token alone; the shutdown-timeout CTS
was only created afterwards. The outbound channel is bounded, and a worker that
has stopped reading the pipe parks the write loop in a pipe write it can only
leave through _stopCts while the channel behind it fills with commands and
their cancels. Once Closing, the heartbeat watchdog skips every tick, so nothing
cancelled _stopCts and the enqueue waited for the caller's token - the lease
sweep's stopping token, which fires at host stop and never before - with
GatewaySession.CloseAsync holding its close lock throughout.

Create the linked timeout CTS before the enqueue and run the enqueue, the loop
waits and the process-exit wait on it, so the whole call is bounded by the
shutdown timeout. A stalled queue now surfaces as the existing ShutdownTimeout
fault, which cancels the loops, kills the worker process tree and hands
GatewaySession its kill fallback.

Test: WorkerClientTests.ShutdownAsync_WhenOutboundQueueIsFullBehindStalledPipeWrite_TimesOutAndKillsWorker
stalls the gateway-side stream (a worker that stopped reading), fills the
outbound channel with abandoned commands and their cancels, and asserts that
ShutdownAsync throws ShutdownTimeout inside the test bound and kills the fake
worker. It hung past the bound before the fix.
2026-10-22 06:10:52 -04:00
Joseph Doherty b2a7fc525d Merge stream I (Rust client: async CA read, typed diagnostics) 2026-10-22 06:10:07 -04:00
Joseph Doherty 3a1ee28729 Merge stream F (.NET client: no ack retry, shared channel, safe dispose, typed diagnostics, AttachSession) 2026-10-22 06:09:55 -04:00
Joseph Doherty c18566bcf2 Merge stream G (Java client: configurable event queue, overflow retention, interrupt cancel, typed diagnostics) 2026-10-22 06:09:55 -04:00
Joseph Doherty 0345726a79 fix(rust-client): async CA file read; typed ping/session-state/worker-info/drain-events 2026-10-22 06:09:51 -04:00
Joseph Doherty 6f0b408833 fix(dotnet-client): no ack retry, caller-owned channels, safe session dispose, typed diagnostics, AttachSession 2026-10-22 06:09:25 -04:00
Joseph Doherty 48c9323d4a fix(java-client): configurable event queue, keep events on overflow, cancel on interrupt, typed diagnostics
- eventQueueCapacity option (default 1024, was hard-coded 16)
- overflow retains buffered events and delivers the error after them
- interrupt cancels the gRPC call and restores the interrupt flag
- ping/getSessionState/getWorkerInfo/drainEvents on MxGatewaySession
2026-10-22 06:09:25 -04:00
Joseph Doherty 7636573ebb Merge stream H (Go client: event buffer option, grpc.NewClient, typed diagnostics) 2026-10-22 06:08:48 -04:00
Joseph Doherty a3a7bd2170 feat(go-client): configurable event buffer, grpc.NewClient dial, typed diagnostics
- Options.EventBufferSize (default 1024) replaces the fixed 16-slot Events buffer
- Dial/DialGalaxy use grpc.NewClient + explicit READY wait (replaces DialContext+WithBlock)
- Session.Ping/GetSessionState/GetWorkerInfo/DrainEvents typed diagnostics
2026-10-22 06:08:31 -04:00
Joseph Doherty 29d4e51c7b Merge stream J (Python typed diagnostics) into fix/review-2026-10-21 2026-10-22 06:07:17 -04:00
Joseph Doherty d716dec160 docs(python): trim diagnostics note 2026-10-22 06:06:56 -04:00
Joseph Doherty 99347d3f00 feat(python): typed ping/get_session_state/get_worker_info/drain_events session helpers 2026-10-22 06:06:54 -04:00
Joseph Doherty 488649ce99 Merge branch 'feat/ackmsg-as-operator' into main (PR #162)
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m22s
ci / java (push) Successful in 2m7s
ci / portable (push) Failing after 4m10s
AcknowledgeAlarm acks by writing the comment to <alarm>.AckMsg authenticated
as the operator, so the Historian records the real operator. Fixes #164,
fixes #165, fixes #166.

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 08:52:19 -04:00
Joseph Doherty 02228b2717 Merge branch 'main' into feat/ackmsg-as-operator
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m7s
ci / java (pull_request) Successful in 2m18s
ci / nightly-windev (pull_request) Has been skipped
ci / portable (push) Failing after 4m47s
ci / windows-x86 (pull_request) Successful in 2m40s
ci / portable (pull_request) Failing after 4m43s
ci / windows-x86 (push) Successful in 5m38s
Picks up the System.Security.Cryptography.Pkcs 10.0.13 pin (NU1903).

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:28:45 -04:00
Joseph Doherty fb20997dc9 Merge branch 'fix/pkcs-cve-pin' into main
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m47s
ci / java (push) Successful in 2m6s
ci / portable (push) Successful in 8m57s
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:24:10 -04:00
Joseph Doherty 463748e395 fix(worker): classify ack interop failures at the COM call; ignore late completions
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m24s
ci / java (push) Successful in 2m32s
ci / nightly-windev (pull_request) Has been skipped
ci / java (pull_request) Successful in 2m38s
ci / portable (push) Failing after 2m52s
ci / portable (pull_request) Failing after 2m53s
ci / windows-x86 (pull_request) Successful in 8m8s
Address review of the #164-#166 fixes:

- The HRESULT check could not tell interop from programming errors
  (a managed ArgumentException already defaults to 0x80070057). Wrap each
  MXAccess call (AuthenticateUser, AddItem, Advise, Write) in a narrow
  filter that converts an interop failure to a private marker exception,
  and catch only that marker, so exceptions from the writer's own code
  (argument checks, caching, the pump) propagate.
- A late OnWriteComplete for a dropped handle recreated the cache entry
  that DropItem removed. The writer now records completions only for item
  handles it holds on its live registration.
- The item-handle-reuse probe now asserts that handles within one
  registration strictly increase, so a rerun is a regression check.

Refs #164, #165, #166

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:18:02 -04:00
Joseph Doherty bd82036d5f docs(worker): record live item-handle-reuse probe for ack writer
ci / windows-x86 (push) Successful in 1m29s
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m47s
ci / nightly-windev (pull_request) Has been skipped
ci / portable (push) Failing after 3m1s
ci / portable (pull_request) Failing after 1m46s
ci / windows-x86 (pull_request) Successful in 1m24s
ci / java (pull_request) Successful in 2m36s
Add the Skip-gated ItemHandleReuseProbeTests probe and its windev result
(2026-10-20): within one MXAccess registration, item handles increase
monotonically and are never reused across remove/re-add, so the ack
writer's Unconfirmed drop-and-re-add mitigation holds and no per-handle
generation counter is needed. Numbering restarts only on a new
registration, which teardown already isolates. Document the guarantee at
the Dirty re-add and in AlarmProbeFindings.md.

Fixes #166

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:13:31 -04:00
Joseph Doherty f13950e453 fix(worker): forget write-completion entries for dropped ack item handles
Add MxAccessWriteCompletionCache.Remove and RemoveServer and call them
from AlarmAckWriter when it drops an item (dirty re-add, LRU eviction,
failed AddItem/Advise/Write) and when it tears down its registration, so
the cache no longer keeps one entry per dropped handle for the life of
the worker.

Fixes #165

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:11:37 -04:00
Joseph Doherty 219611c821 fix(worker): narrow AlarmAckWriter ArgumentException interop filter
Resolve the operator user, password and comment before the interop try
blocks, and match ArgumentException as an MXAccess failure only when it
carries E_INVALIDARG and is not ArgumentNullException or
ArgumentOutOfRangeException. A programming error inside the try blocks
now propagates instead of being reported as an authentication or write
failure.

Fixes #164

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:11:37 -04:00
Joseph Doherty a09fa0a4d6 fix: pin System.Security.Cryptography.Pkcs 10.0.13 (NU1903)
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m23s
ci / java (push) Successful in 2m19s
ci / portable (push) Successful in 10m44s
Microsoft.Data.SqlClient 6.0.2 pulls System.Security.Cryptography.Pkcs 9.0.4,
which carries GHSA-846f-f764-76mx (high) and GHSA-2fmv-rv2c-59x7 (moderate).
With TreatWarningsAsErrors the NU1902/NU1903 audit warnings failed restore.
10.0.13 patches both.

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-20 07:07:59 -04:00
Joseph Doherty 3e988ede88 docs: record gateway-level live verification of AckMsg-as-operator acks
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m21s
ci / nightly-windev (pull_request) Has been skipped
ci / java (push) Successful in 2m3s
ci / portable (push) Failing after 2m19s
ci / windows-x86 (pull_request) Successful in 1m19s
ci / java (pull_request) Successful in 2m2s
ci / portable (pull_request) Failing after 2m17s
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 20:07:36 -04:00
Joseph Doherty e7f56622c1 fix: address final ackmsg-as-operator review findings
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m0s
ci / windows-x86 (push) Successful in 2m11s
ci / portable (push) Failing after 2m21s
- AlarmAckWriter: classify AuthenticateUser and AddItem/Advise/Write
  failures by HRESULT for any interop exception (ArgumentException for
  E_INVALIDARG, UnauthorizedAccessException, COMException); server-gone
  HRESULTs at AuthenticateUser tear down the registration and report
  WriteRejected.
- Bound the ack item cache with a 256-entry LRU (eviction best-effort
  UnAdvise+RemoveItem).
- After an Unconfirmed ack, drop and re-add the item before the next
  write so a late OnWriteComplete on the old handle is not credited.
- Pass StaRuntime.PumpPendingMessages to the ack writer through a
  pump-aware alarm handler factory so long completion waits refresh the
  STA watchdog heartbeat.
- Rust CLI: --operator is required for acknowledge-alarm.
- Docs: full ack status table, credential-oracle trade-off, upgrade note.

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:58:24 -04:00
Joseph Doherty 1998ee3e20 docs: address Task 12 review (supersede stale ack steps, outcome rows)
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m10s
ci / windows-x86 (push) Successful in 2m11s
ci / portable (push) Failing after 2m18s
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:45:43 -04:00
Joseph Doherty 027cc7393d fix(python-client): redact operator password from AcknowledgeAlarmReply errors
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:45:02 -04:00
Joseph Doherty 7b81cb7f72 feat(python-client): operator credentials on acknowledge-alarm
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:45:02 -04:00
Joseph Doherty 0143591858 docs(dotnet-client): keep XML docs with their CLI members
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:44:56 -04:00
Joseph Doherty e8ace39418 feat(dotnet-client): operator credentials on acknowledge-alarm
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:44:56 -04:00
Joseph Doherty 9907a97cb0 fix(java-client): redact operator password on async acknowledge-alarm failures
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:44:32 -04:00
Joseph Doherty 84e04b6552 feat(java-client): operator credentials on acknowledge-alarm
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:44:32 -04:00
Joseph Doherty 417a47bdb5 feat(rust-client): operator credentials on acknowledge-alarm
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:44:32 -04:00
Joseph Doherty cfe9f817a3 feat(go-client): operator credentials on acknowledge-alarm
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:44:32 -04:00
Joseph Doherty 9409488008 docs: alarm acknowledgement writes AckMsg as the authenticated operator
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:44:21 -04:00
Joseph Doherty 2858e42e80 feat(gateway): forward operator credentials on alarm acknowledgement
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:40:23 -04:00
Joseph Doherty d6db0ff40d feat(worker): route alarm acks through the operator-authenticated AckMsg writer
ci / nightly-windev (push) Has been skipped
ci / java (push) Failing after 2m5s
ci / windows-x86 (push) Successful in 2m10s
ci / portable (push) Failing after 2m17s
AlarmCommandHandler -> AlarmDispatcher -> IAlarmAckWriter: the dispatcher resolves the ack item through the consumer and the writer authenticates the operator and writes the comment. The executor builds AlarmAckOperator from operator_user/domain/password, maps AlarmAckOutcome to the reply (Applied/Unconfirmed Ok, AlarmNotFound/NotAckable InvalidRequest, AuthenticationFailed/WriteRejected MxaccessFailure) and reports escaping exceptions by type and HRESULT only, so operator_password cannot leak. Removes the inert managed/WNAL alarm-client ack path.

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:35:55 -04:00
Joseph Doherty 0d4b2aea2d refactor(worker): alarm consumers resolve the AckMsg item instead of acking
IMxAccessAlarmConsumer.ResolveAckTarget/ResolveAckTargetByName replace AcknowledgeByGuid/AcknowledgeByName. wnwrap maps Galaxy-provider alarms to <tag>.AckMsg and drops its inert parallel ack consumer; subtag resolves to the target's AckCommentSubtag and no longer advises or writes it; ISubtagAlarmSource.Write is removed. Does not build alone: AlarmDispatcher moves to the new API in the next commit.

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:35:49 -04:00
Joseph Doherty 8199553734 fix(worker): handle AckMsg add/advise/write COM failures and server death
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m55s
ci / java (push) Failing after 2m2s
ci / portable (push) Failing after 2m17s
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:25:43 -04:00
Joseph Doherty 7900df3dce fix(worker): disambiguate MxStatusCategory in AlarmAckWriter
ci / nightly-windev (push) Has been skipped
ci / java (push) Failing after 2m25s
ci / portable (push) Failing after 2m26s
ci / windows-x86 (push) Successful in 2m27s
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:22:27 -04:00
Joseph Doherty 76db822df3 feat(worker): acknowledge alarms via authenticated AckMsg write
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Failing after 56s
ci / java (push) Failing after 2m17s
ci / portable (push) Failing after 2m23s
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:21:55 -04:00
Joseph Doherty 64dc1215fd feat(worker): attribute alarm ack transitions from wnwrap ACK fields
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 2m7s
ci / java (push) Failing after 2m17s
ci / portable (push) Failing after 2m23s
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:18:24 -04:00
Joseph Doherty a94b502ce6 feat(contracts): carry operator credentials on alarm acknowledgement
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
2026-10-19 19:16:39 -04:00