| 2026-07-13 |
Initial tracking doc generated from the six domain remediation designs. All 47 findings Not started (IPC-31, SEC-35 N/A). |
| 2026-07-13 |
TST-25/TST-26 → In progress (branch fix/tst-25-windev-ci). Added scripts/ci/{windev-worker-ci.ps1,run-windev-ci.sh,windev.known_hosts}, windows-x86 (per-push) + nightly-windev (scheduled) jobs in ci.yml, and the TST-26 doc/comment fixes (GatewayTesting.md, Contracts.md, check-codegen.ps1). Mechanism hand-verified on windev: build→0, bogus-SHA→nonzero (lock released), test→356 passed/0 failed in ~50s (per-push stays test, no demotion), and run-windev-ci.sh SSH+EncodedCommand exit-code propagation confirmed. |
| 2026-07-13 |
Operator bring-up complete: dedicated CI ed25519 key installed in windev administrators_authorized_keys (authorized into dohertj2, which owns the working MXAccess/toolchain env — a fresh OS account would break the build; the key is independently revocable), Gitea secrets WINDEV_SSH_KEY/WINDEV_SSH_KNOWN_HOSTS + variable WINDEV_SSH_USER=dohertj2 stored, runner→10.100.0.48:22 egress verified on the traefik net, issue-write confirmed. TST-25/TST-26 → Done: credentialed windows-x86 ran GREEN on d769244 (Gitea run #37) — Linux runner SSHed windev, checked out the SHA in C:\build\mxaccessgw-ci under lock, ran the x86 Worker build + Worker.Tests, exit 0; nightly-windev correctly skipped on the push event. Branch merged to main. Follow-ups (old tracker): revisit TST-05 (scheduled live smoke — now covered by nightly-windev) and TST-24 (client wire tests) which this unlocks. |
| 2026-07-13 |
Ran the TST-25 acceptance checks (scripts/ci/README.md) — they caught two real CI defects, both fixed on fix/tst-25-ci-key-log-leak: (1) CI SSH key leaked in cleartext in the windows-x86 step env echo (Gitea's line-oriented masker missed the multiline PEM) — rotated the CI key on windev (old pubkey revoked), stored the key base64-encoded so the masker redacts it to *** (confirmed on run #38), taught run-windev-ci.sh to decode, dropped the redundant public known-hosts secret from the job env; (2) bootstrap lock race — run-windev-ci.sh's pre-hand-off git fetch/checkout ran outside the worktree lock, so concurrent runs collided on .git/index.lock; the bootstrap now holds the lock (ps1 re-uses it via MXGW_CI_LOCK_HELD), retest confirmed clean serialization. Also deflaked SessionManagerTests fail-fast timing assertions (absolute <100ms wall-clock bound flaked under CI load; now anchored to the configured timeout / dropped for the zero-timeout case). Checks passed: unreachable-host fast-fail (exit 255/15s), deliberate-red propagation (Worker.Tests failure → exit 1), lock concurrency (2nd run waits), no-key-in-logs (masked). Merge target df7e20d verified GREEN via the local windev path (Worker build + 356 tests); merged to main 19cbf7b. Check 6 (forced-failure nightly issue): issue endpoint+token proven live at bring-up (#124); in-CI forced-failure probe abandoned to shared-runner congestion (residual if: failure() gating is standard Actions). |
| 2026-07-13 |
New finding TST-30 (Low/P2) added — surfaced during TST-25 acceptance: CI runs on a single shared gitea-runner (maxParallel=1, co-located 10.100.0.35) interleaved with dohertj2/lmxopcua, and Gitea 1.26 exposes no run cancel/delete, so queue latency is unbounded under cross-repo contention and the runner is a single point of failure. Design: add a second/labelled runner + document the no-cancel reality and the run-windev-ci.sh bypass. Roll-ups updated (Testing Low 2→3, total 47→48; P2 9→10). |
| 2026-08-07 |
TST-29 → Done: migrated the Phase-5 (orphan-worker reattach) deferred-not-planned governance record and the settled Phase-4 Viewer-default decision from oldtasks.md into a new "Session-Resilience Epic Scope" entry in docs/DesignDecisions.md; repointed CLAUDE.md and stillpending.md:7,165 from oldtasks.md to docs/DesignDecisions.md / docs/plans/2026-06-15-session-resilience.md.tasks.json; git rm oldtasks.md. The five untracked root docs-review artifacts (MxAccessGateway-docs-{issues,fixed,final}.md, MxGatewayClient-docs-{issues,fixed}.md) were absent from this worktree — delete from the main working tree separately. |
| 2026-08-07 |
GWC-24 → Done (branch fix/gwc-24-staging-bound). WorkerClient._eventStaging is now Channel.CreateBounded at 2 × EventChannelCapacity (Wait, single reader/writer, no sync continuations); a rejected staging TryWrite faults the client ProtocolViolation with QueueOverflow("worker-event-staging") unless IsTerminalState() (shutdown stays a silent drop), so a consumer draining slower than its worker produces dies at a fixed ceiling instead of growing gateway memory. Queue-depth accounting moved from EnqueueWorkerEventAsync to StageWorkerEvent, so the single gauge reports staged + queued; the timed-write fault (EventChannelFullModeTimeout / QueueOverflow("worker-events")) is unchanged and still catches the full-stall case first. No new config key — total gateway-side buffering is 3 × MxGateway:Events:QueueCapacity, derived; coordination with still-open old GWC-21 (EventChannelFullModeTimeout configurability) remains open and was not blocked on. Docs same commit: GatewayProcessDesign.md (two overflow faults), MxAccessWorkerInstanceDesign.md, GatewayConfiguration.md, Metrics.md. Tests: WorkerClientTests.StagingChannelOverflowFaultsWorkerWithoutWaitingForFullModeTimeout and .WorkerEventQueueDepthGaugeCountsStagedEvents; WorkerClientTests 22/22 green, NonWindows.slnx builds with 0 warnings. |
| 2026-08-07 |
ReplayGap end-to-end cluster (GWC-25 + CLI-35 + CLI-36) → Done on fix/gwc-25-replaygap-trio. GWC-25: SessionEventDistributor.RegisterWithReplay's empty-ring branch now reports oldestAvailableSequence = _highestSequenceSeen + 1 when gap == true (still 0 when no gap), so the universal oldest - 1 resume formula no longer wraps to ulong.MaxValue and dead-stream the subscriber; docs/Sessions.md documents the empty-ring value. CLI-35: the Python CLI renders a ReplayGap as a {"replayGap": {...}} row via a new _event_row helper instead of crashing in MessageToDict. CLI-36: the Go CLI branches on result.IsReplayGap() and prints the typed REPLAY_GAP requested_after=<n> oldest_available=<n> line / replayGap JSON row instead of formatting the library's cleared Event. docs/CrossLanguageSmokeMatrix.md gained a per-CLI gap-rendering table (one edit covering both client findings). Four new tests as designed (3 × SessionEventDistributorTests, GatewayEndToEndReconnectReplayTests.ReconnectAfterFullAgeEvictionResumesWithSentinelFormula) plus test_stream_events_renders_replay_gap (Python) and TestRunStreamEventsPrintsReplayGap (Go); all written red first and each reproducing its defect verbatim. Deferred: GWC-25's ReplayGap.oldest_available_sequence proto-comment amendment is not in this change — it is comment-only but triggers the full five-client regen fan-out, so it lands with the later codegen wave (alongside IPC-23's proto-comment edits) rather than forcing a regen for one sentence. Note for that wave: the fake-worker gateway e2e suite cannot run on the macOS worktree without TMPDIR shortened (macOS caps the Unix-domain-socket path backing .NET named pipes at 104 chars; TMPDIR=/tmp dotnet test … works and was used here). |
| 2026-08-07 |
GWC-27 → Done, GWC-26 → Done (branch fix/gwc-26-27-alarm-attach). GWC-27: GatewaySession.AttachInternalEventSubscriber now mirrors AttachEventSubscriber's readiness gate under _syncRoot, before EnsureDistributorCreated, so a premature attach can no longer latch a poisoned distributor. GWC-26: the alarm monitor takes its internal lease directly from the session before SubscribeAlarms and drains it after the first reconcile; ISessionManager.ReadAlarmEventsAsync removed (zero remaining callers); ApplyReconcile now broadcasts an Acknowledge feed transition for a both-present alarm whose state advanced to ActiveAcked (feed-level repair on AlarmFeedMessage, not MxEvent synthesis). New tests GatewaySessionTests.AttachInternalEventSubscriberBeforeReadyThrowsAndDoesNotPoisonDistributor and GatewayAlarmMonitorAttachOrderTests (TransitionsDuringSubscribeWindow_StillReachTheAlarmFeed, ApplyReconcileBroadcastsAcknowledgeDelta); the alarm-monitor fakes now hand the monitor a real Ready GatewaySession with a dashboard mirror so the window is actually reproducible. Verification: NonWindows build 0 warnings/0 errors; GatewayAlarmMonitor 16 passed, SessionManagerTests 38 passed, GatewaySessionTests 19 passed, AlarmFailoverEndToEndTests 2 passed. |
| 2026-08-07 |
Code review of fix/gwc-26-27-alarm-attach surfaced a known pre-existing characteristic, now documented: the alarm monitor's reconcile-derived feed repairs are at-least-once, not exactly-once. A reconcile reads the worker's current state while the matching live transition may still be buffered in the monitor's internal lease, so both broadcast and the duplicates are indistinguishable on the alarm feed (StreamAlarms + dashboard alarm hub). This pre-dates GWC-26 — the Raise/Clear presence repair has always had it, since nothing serializes a reconcile pass against the in-flight live stream — so closing it (reconcile/live serialization or transition-timestamp dedup) was ruled out of scope for a P2 fix. Documented instead in GatewayAlarmMonitor.ApplyReconcile, gateway.md, and docs/Sessions.md, with the consumer-side contract stated explicitly (apply transitions idempotently — "set this alarm to this state", never increment/toggle). Candidate finding for the next review cycle. |
| 2026-08-07 |
CLI-37 + CLI-38 -> Done (branch fix/cli-37-38-conformance), one cross-client conformance commit; closes old-tracker CLI-08. Canonical rules landed everywhere: an MxStatusProxy entry fails iff category != MX_STATUS_CATEGORY_OK (success is the raw COM member, diagnostics only; absent entry = success, present entry with UNSPECIFIED = failure), and a reply fails on HRESULT iff hresult is present and < 0 (so S_FALSE = 1 passes). Edits: .NET MxStatusProxyExtensions.IsSuccess (drop the Success != 0 conjunct) + MxCommandReplyExtensions (!= 0 -> < 0); Go StatusSucceeded (category) + errors.go (< 0); Java MxStatuses.succeeded (category, Javadoc corrected) + MxGatewayErrors (< 0); Python errors.py (category); Rust ensure_mxaccess_success (category, doc comment corrected). Four shared fixtures added under clients/proto/fixtures/behavior/command-replies/ (write.status-category-{error-success-set,ok-success-zero}.reply.json, write.hresult-{s-false,e-fail}.reply.json) + manifest + docs/ClientBehaviorFixtures.md; each of the five suites now runs all four fixture-driven, plus a per-language table test for the two edges fixtures cannot express (nil/null entry, UNSPECIFIED category). Docs same commit: ClientLibrariesDesign.md per-item rule sentence (its existing HRESULT < 0 claim is now true), .NET/Go/Java README error sections. Also fixed a Java test fake that built a status with a bare setSuccess(1) and no category. Verification: dotnet build 0 warnings + 110 passed/1 skipped; gofmt -l clean, go build ./..., go test ./... all ok; gradle test BUILD SUCCESSFUL with no generated-file churn to revert this time (no .proto changed and generateProto stayed up to date); python -m pytest 155 passed/1 skipped; cargo fmt (no unrelated reformat), cargo check, cargo test --workspace 100 passed, cargo clippy --all-targets -- -D warnings clean. Gateway-side ClientBehaviorFixtureTests 8/8 re-run because the new fixtures are validated there. |
| 2026-08-07 |
CLI-45 → Done on fix/cli-45-credential-envvar. All five CLIs now share one credential contract for authenticate-user: flags --password / --password-env (Go: -password / -password-env) defaulting to env MXGATEWAY_VERIFY_PASSWORD, resolution flag-then-env, and a resolved credential that is missing or empty is a usage error naming the flag and the variable — never the value, never sent to the wire. Go and Java previously authenticated with an empty password: Go now returns the guard error before dialing, Java throws a picocli ParameterException instead of falling back to "". Python's --password-env gained the canonical default (its UsageError was already conformant) and its message now names the resolved variable. Rust treats an empty --password or empty env value as missing (resolution extracted into a testable resolve_verify_user_password). .NET adopted the canonical flags and keeps its pre-existing names as deprecated aliases for one release — order: --password, --verify-user-password, the variable named by --password-env (or the deprecated --verify-user-password-env; default MXGATEWAY_VERIFY_PASSWORD), then MXGATEWAY_VERIFY_USER_PASSWORD. Tests: TestRunAuthenticateUser{RejectsEmptyPassword,ReadsPasswordFromCanonicalEnv} (Go), 3 picocli cases (Java), 3 click cases (Python), 2 clap/resolver cases (Rust), 4 xUnit cases covering the canonical flag, both env-name paths, the deprecated flag+env aliases, and the missing/empty failure (.NET). Docs same commit: docs/CrossLanguageSmokeMatrix.md gained a "Credential contract for authenticate-user" section and the per-CLI subcommand-coverage table — the half of this finding that is documented rather than fixed (.NET exposes all nine single-item session commands; Rust unregister + the credential pair; Go/Python/Java the credential pair only; verified against each dispatch table, and every gap is CLI surface only since all five libraries implement all nine helpers). All five client READMEs name the canonical variable and the fail-fast rule; the .NET README gained an authenticate-user credentials section carrying the deprecation note. Deviation: Java keeps isBlank() (per this design's "null or blank" wording for Java) where the other four test emptiness, so a whitespace-only credential is additionally rejected there. Verification (all five, on macOS): Go gofmt -l . clean, go build ./... clean, go test ./... ok; Java JAVA_HOME=/opt/homebrew/opt/openjdk@17 gradle test BUILD SUCCESSFUL, CLI suite 51 tests / 0 failures — no generated-tree churn appeared this run, git status for clients/java/**/generated clean with no revert needed (no .proto changed); Python python -m pytest 148 passed / 1 skipped (TLS opt-in); .NET dotnet build …Client.slnx 0 warnings / 0 errors and client tests 108 passed / 1 skipped (live-gateway opt-in); Rust cargo fmt (diff confined to the new code), cargo check --workspace, cargo test --workspace 100 tests across 6 targets all green, cargo clippy --all-targets -- -D warnings clean. |
| 2026-08-07 |
SEC-33 + SEC-34 → Done, SEC-35 discharged (doc-only) (branch fix/sec-33-34). SEC-33: IsRootedForAnyPlatform deleted and rooting made host-meaningful (Path.IsPathRooted); AddIfNotRooted/AddIfInvalidPath promoted to a shared GatewayConfigPathRules helper; both C:\ProgramData\... literals (Authentication:SqlitePath, Galaxy:SnapshotCachePath) removed from appsettings.json so the CommonApplicationData-derived code defaults take over; new GalaxyRepositoryOptionsValidator (ValidateOnStart) enforces a valid, host-rooted SnapshotCachePath when PersistSnapshot; the Galaxy default is seeded as a configuration value before AddZbGalaxyRepository (deviation: the package's SnapshotCachePath is init-only, so a PostConfigure mutation would not compile — same effect). Stray-file root cause: host start eagerly builds AuthSqliteConnectionFactory, which under the Windows literal materialized a junk-named relative auth DB under the test bin/ on macOS; the three real-host-start tests now pin SqlitePath to a temp path (find src -name 'C:*' empty). SEC-34: window-3 Invalidate race fixed with a per-key generation counter (bump-before-evict, snapshot-then-recheck); window-2 expiry cap took the documented fallback because the library verification identity carries no ExpiresUtc (donor-library ask recorded) — so only Invalidate_DuringInFlightVerification_DiscardsStaleRepopulation was added, not the two expiry-cap tests. GWC-24 rider: MxGateway:Events:QueueCapacity gained an int.MaxValue/2 upper bound so checked(2 * EventChannelCapacity) in WorkerClient cannot overflow at session creation (+ two validator tests). SEC-35: doc note added to docs/GatewayConfiguration.md (IsProduction() env-name semantics). Docs same commit: GatewayConfiguration.md, Authentication.md. Evidence (macOS): dotnet build …Server 0 warnings/0 errors; --filter ~GatewayOptionsValidator 69/69, ~GalaxyRepositoryOptionsValidator 5/5, ~CachingApiKeyVerifier 10/10, ~GatewayTreeHygiene 1/1. Code-review follow-ups (same branch): (a) two missed same-commit docs updated — docs/GalaxyRepository.md (SnapshotCachePath per-OS default + rooting validator) and A2-galaxyrepository-adoption-handoff.md (the "NSSM SnapshotCachePath required or persistence silently no-ops" caveat corrected — the override is now optional, blank → rooted host default; the option-validation item repointed to the new validator). (b) SEC-34 TryParseKeyId guard confirmed: the _ split cannot truncate a key id because both — and the only — gateway key-creation paths (ApiKeyAdminCommandLineParser.IsValidKeyId, DashboardApiKeyManagementService.ValidateKeyId) restrict key ids to `IsAsciiLetterOrDigit |
| 2026-08-07 |
SEC-31 + SEC-32 → Done (branch fix/sec-31-32-limiter, one change set as planned). ApiKeyFailureLimiter reworked from IsBlocked/RecordFailure/Reset(string peer) to a partition-pair API (Check/RecordFailure/Reset(ApiKeyThrottlePartition) returning ApiKeyThrottleDecision): layer 1 is the composite (transport peer, key id) partition, layer 2 a per-key-id aggregate across peers (ApiKeyFailureAggregateLimit, default 30), and an over-limit state now admits one probe per ApiKeyFailureProbeIntervalSeconds (default 5) instead of blocking absolutely — so a success can reset the state while throttled, killing the 10-packets-per-minute lockout. SEC-32 rides along: the interceptor validates token shape (mxgw prefix, ≥3 non-empty _ segments, key id ≤ 64 chars) before minting a key-id partition, each peer may mint at most 32 of them (overflow collapses to its fallback partition), and eviction prefers expired windows, never dropping an over-limit partition below a 2× transient overshoot ceiling. New counter mxgateway.auth.throttled tagged stage=peer|aggregate only (no key material — /metrics is still unauthenticated per open SEC-14). Docs updated in the same commit (docs/GatewayConfiguration.md limiter rows + two new keys, docs/Authentication.md hot-path paragraph, docs/Authorization.md SEC-11 section, limiter/SecurityOptions XML remarks). Evidence: dotnet build …Server clean; --filter ~ApiKeyFailureLimiter 11/11 passed (new ApiKeyFailureLimiterTests), --filter ~GatewayGrpcAuthorizationInterceptor 20/20 passed (incl. the four SEC-31 contract tests and NonMxgwToken_FallsBackToTransportPeerPartition), --filter ~GatewayOptionsValidator 66/66 passed. Full suite on macOS: 804 passed / 44 failed — all 44 are the pre-existing named-pipe fake-worker classes (WorkerClientTests, FakeWorkerHarnessTests, SessionWorkerClientFactoryFakeWorkerTests, GatewayEndToEnd*), verified identical (44) on the unmodified tree. Follow-up unchanged: the new MxGateway:Security keys belong in old SEC-24's effective-config projection when that is picked up. Code review of the branch found two defects in the first pass, both fixed before merge: (1) probe admission was check-then-act across two lock scopes, so a burst arriving at an interval boundary could all observe "due" and all be admitted — the claim is now a single critical section (TryConsumeProbe), and because the two layers are claimed one at a time, a slot claimed on the partition is compensated (ReleaseProbe) when the aggregate then refuses; (2) Reset on a success whose key id had been collapsed into the address's shared fallback partition removed that shared partition, letting one authentication wipe an in-progress spray from the same address — it is now left to decay by window expiry, while the key's aggregate is still cleared. Tests added: ProbeAdmission_UnderConcurrentArrivals_GrantsExactlyOneSlot, ProbeAdmission_WhenAggregateRefuses_ReturnsTheClaimedPeerSlot, Reset_WithOverCapKeyId_DoesNotClearSharedFallbackPartition. A second review pass found a residual defect in that compensation path: the release identified its own reservation by comparing NextProbeAtTicks to now + interval, the identical expression a failure re-arm writes — so a concurrent RecordFailure on the same state sharing a clock tick (routine at ~1 ms resolution) was mistaken for the caller's own claim and stomped back to the stale, already-due value, prematurely reopening the probe slot. Replaced with a monotonic per-state ProbeVersion bumped by every writer of NextProbeAtTicks (claim and re-arm alike); the release restores only when the version still matches the one its claim stamped, and bumps it again on restore so no other stale release can match. Covered by ProbeSlotRestore_DoesNotStompConcurrentRearmAtSameTick, made deterministic by a new internal ProbeReleaseInterleaveHook test seam (null in production, one null check on the refused path) because the claim-to-release window is nanoseconds wide and racing threads cannot hit it reliably — verified as a genuine red against the timestamp guard (Expected: ThrottledByPeer / Actual: ProbeAdmitted). Limiter suite 11 → 15. |
| 2026-08-07 |
CLI-40 + CLI-41 + CLI-44 → Done (branch fix/cli-40-41-44), one change set; two fast-follow riders from the CLI-37/38 review landed alongside. CLI-40 (exact-secret credential scrub, ported to Rust/Java/.NET; Go/Python already did it): every credential-bearing helper (AuthenticateUser password, WriteSecured/WriteSecured2 string payloads) now scrubs the exact caller-supplied secret from any surfaced error text, on top of the by-construction guarantee — Rust MxAccessError gained a secrets: Vec<String> field whose Display scrubs exact-then-pattern (and a hand-written redacting Debug, since the derived Debug would have leaked the reply verbatim — caught by the existing Debug regression test); Java added MxGatewaySecrets.redactExact + a private invokeCommandRedacted(command, secrets…) that rebuilds the same exception type with the redacted message and does not chain the secret-bearing original as cause; .NET added an internal MxGatewaySecretRedaction (rebuilds the same concrete MxGateway*Exception type via a type switch) wired into the three credential helpers — and it carries the original's inner cause forward rather than the secret-bearing original, so ToString() (what loggers emit) is scrubbed too, not just Message (locked by a ToString() assertion). CLI-41 (uniform malformed-reply contract for AuthenticateUser/ArchestrAUserToId/AddBufferedItem across all five): typed payload → present return_value with the int32 variant → else a typed malformed-reply error (MalformedReplyError Go/Python, MxGatewayMalformedReplyException Java/.NET, existing Error::MalformedReply Rust) — never a proto3 default 0, never an NRE (fixes the Go/Java silent-0, .NET NRE, and Rust's own internal inconsistency by giving authenticate_user_id/archestra_user_id the same return_value fallback add_buffered_item_handle already had). CLI-44 (Go): the event goroutine's Recv-error path now uses a new non-blocking sendTerminalEventResult on the reserved slot instead of sendEventResult, so a genuine terminal gRPC error is reported as itself even when the 16 data slots are full, rather than being mislabeled ErrSlowConsumer; test TestEventsFullBufferTerminalErrorKeepsRootCause was confirmed red-first (a 250 ms settle after streamDone is required to make the buffer genuinely full at error time). Three shared fixtures added under clients/proto/fixtures/behavior/command-replies/ (authenticate-user.{echoed-credential,missing-payload,return-value-only}.reply.json; the echoed-credential reply uses an OK envelope + negative HRESULT + the credential in protocolStatus.message / statuses[0].diagnosticText / diagnosticMessage so all five clients route it to their MXAccess error uniformly) + manifest + docs/ClientBehaviorFixtures.md + docs/ClientLibrariesDesign.md. Rider (a): .NET ToDiagnosticSummary and Python _mxaccess_message now surface the raw success member (Rust already did), for diagnostics-only parity. Rider (b): the status-conversion fixture gained an independent wantSuccess boolean per case; the Go TestStatusConversionFixtures and .NET FixtureStatuses_ProjectSuccessAndPreserveRawFields now assert against it instead of recomputing category == OK (the formula under test). Deviation: the <redacted> marker is not universal — Go/Rust/Java use <redacted>, Python and the .NET CLI use [redacted]; each suite asserts its own client's marker plus the exact-secret absence (marker unification was out of scope). Verification (all five, on macOS): Go gofmt -l clean + go build ./... + go test ./... ok; Python python -m pytest 162 passed / 1 skipped; .NET dotnet build …Client.slnx 0 warnings + client tests 120 passed / 1 skipped; Rust cargo fmt + cargo check --workspace + cargo test --workspace (all targets pass) + cargo clippy --all-targets -- -D warnings clean; Java JAVA_HOME=/opt/homebrew/opt/openjdk@17 gradle test BUILD SUCCESSFUL with no generated-tree churn to revert (no .proto changed). Code review of the branch found three defects, all fixed before merge: (1, all five, critical) the message-only scrub left the server-echoed credential exposed on the redacted error's structured reply accessor (.NET MxAccessException.Reply/Statuses, Java reply()/protocolStatus(), Go MxAccessError.Reply via errors.As, Rust reply()/into_reply(), Python raw_reply) — the redacted error now carries a scrubbed clone of the reply (protocol_status.message, diagnostic_message, statuses[].diagnostic_text all redacted), with per-language tests asserting the reply accessor is clean; docs/ClientLibrariesDesign.md updated to make the "never reaches exception text" claim true for structured fields too. (2, Rust, critical) ensure_command_success routed PROTOCOL_STATUS_CODE_MXACCESS_FAILURE to Error::Command (unlike the other four clients), where attach_secrets did not patch it and its derived Debug/Display leaked the secret — now MxaccessFailure routes to Error::MxAccess (fixing a real cross-client inconsistency; an existing test flipped from Error::Command to Error::MxAccess). (3, Go, important) the CLI-44 sendTerminalEventResult was unconditionally non-blocking, so on the never-drop SubscribeEvents/SubscribeEventsAfter path (cancelWhenResultBufferFull=false) a genuine terminal error under a full buffer hit the default: and was silently dropped — now the terminal send is reserved-slot-non-blocking only for the cancel-on-overflow path and blocking for the never-drop path. A new shared fixture authenticate-user.echoed-credential-mxaccess-failure.reply.json (the echo under MXACCESS_FAILURE) is wired into all five suites. Minors also landed: whitespace-only-secret guard on the .NET/Java redact helpers; Java invokeCommandRedacted now preserves the exception subtype (mirroring .NET's type switch) instead of collapsing to the base type; dedicated redaction-helper unit tests (multiple occurrences, substring-overlap, empty/blank secrets) in Go/Java/.NET. Re-verified all five green (Go go test ./... ok + gofmt clean; Python 163 passed/1 skipped; .NET build 0 warnings + 128 passed/1 skipped; Rust fmt/check/test --workspace/clippy -D warnings all clean; Java gradle BUILD SUCCESSFUL, no generated churn). |
| 2026-08-07 |
GWC-28, GWC-29, GWC-30, TST-28 → Done (branch fix/gwc-28-29-30-polish). GWC-28: WorkerClient.WriteLoopAsync now stamps envelope.Sequence = unchecked(++_nextSequence) immediately before _writer.WriteAsync, and CreateEnvelope leaves it unset; _nextSequence dropped from long + Interlocked to a plain ulong touched only by the write loop (the channel's single consumer, SingleReader = true), so wire order and sequence order are the same thing by construction. Mirrors the worker's WRK-04 stamping, which the gateway half had never received; gateway.md's envelope-sequence rule now states that both sides stamp at write inside their single write path and that inbound enforcement (still open, old GWC-10) would rely on it. New WorkerClientTests.ConcurrentInvokesEmitStrictlyIncreasingSequencesOnTheWire (32 parallel invokes, sequences asserted strictly increasing in wire order) failed 3/3 pre-fix. GWC-29: added MxAccessGrpcMapper.MapCommand(MxCommand); Invoke no longer deep-clones the whole MxCommandRequest just to overwrite and discard its command. The one clone inside MapCommand stays and is documented as required — commandToInvoke may be the gRPC-owned request.Command and is read again after dispatch by TrackCommandReply, so it is what keeps CreateCommandEnvelope's no-aliasing invariant true. New MxAccessGrpcMapperTests.MapCommandFromCommandClonesPayload (isolation + both overloads equal under a FakeTimeProvider). GWC-30: WorkerFrameReader reuses a per-instance _lengthPrefix scratch buffer instead of allocating 4 bytes per frame, with a class remark that ReadAsync is not reentrant (single read loop per WorkerClient; handshake reads complete before the loop starts); guarded by new WorkerFrameProtocolTests.ReadAsync_WithMultipleFramesOnOneReader_ParsesEveryFrame (5 frames, varying payload lengths, one reader). TST-28: new [Theory] WorkerClientTests.StartAsync_SendsGatewayHelloWithConfiguredMaxFrameBytes over the default and a 2 MiB override via FakeWorkerHarness.CreateConnectedPairAsync(maxMessageBytes:) — test-only, and the mutation check (hard-code MaxFrameBytes = 0) failed both cases before being reverted. Verification: NonWindows.slnx 0 warnings/0 errors; WorkerClientTests 25 passed, WorkerFrameProtocolTests 11 passed, MxAccessGrpcMapperTests 6 passed, MxAccessGatewayService* 29 passed, full gateway suite 844 passed / 0 failed (TMPDIR=/tmp on macOS). |
| 2026-08-07 |
WRK-21 + WRK-28 + WRK-23 + IPC-30 → Done (branch fix/wrk-21-drain-cluster, commits 33ba612 + test-fixture follow-ups 7c2eaf0/a256560). WRK-21: MxAccessEventQueue gains a byte-budgeted Drain(maxEvents, maxTotalBytes) returning the new WorkerEventDrainResult, sizing inside the queue lock so an event that will not fit is never dequeued; CreateDrainEventsReply budgets against the negotiated frame max less a 64 KiB wrapper reserve and reports truncation through the existing DiagnosticMessage (no proto change), satisfying IPC-23 R1–R3; both reply-write seams (HandleControlCommandAsync, ProcessCommandAsync) now catch MessageTooLarge and answer the correlation with an InvalidRequest reply instead of unwinding/faulting the session. WRK-28: the 10,000 ceiling moved to GatewayContractInfo.MaxDrainEventsPerCommand, referenced by the gateway validator and the worker clamp (C# const, no .proto change). WRK-23: WorkerFrameWriter peek-stamps then commits Sequence only immediately before the stream write, so rejections leave no wire gap. IPC-30: an oversized event frame stays session-fatal but writes a PROTOCOL_VIOLATION WorkerFault with command_method = EventDrain naming family/handles/sequence/sizes (never the value) before exiting. Docs same commit: MxAccessWorkerInstanceDesign.md, WorkerFrameProtocol.md, gateway.md. IPC-23 → In progress — mechanics landed here; the proto-comment/doc wave (and its regen fan-out) is still pending and must not be folded into this branch. Evidence — macOS: dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx 0 warnings/0 errors, dotnet test …MxGateway.Tests --filter FullyQualifiedName~MxAccessGrpcRequestValidator 4/4 passed. windev (scripts/ci/windev-worker-ci.ps1 -Sha a2565604 -Mode test, 2026-08-07 06:47): x86 Worker build 0 warnings/0 errors, Worker.Tests 367 passed / 0 failed / 11 skipped (skips are the live-MXAccess/dev-rig opt-ins), script exit 0. Harness note: PipePair runs both pipe ends in one process with blocking FlushFileBuffers per frame, so it wedges on multi-MB frames or after ~85 large round trips; the pipe tests therefore negotiate a 128 KiB frame maximum and walk 1,000 events to empty, while the full 10,000-event drain-to-empty no-loss proof runs at the queue layer (MxAccessEventQueueTests). |
| 2026-08-07 |
Code-review follow-ups on the same branch (commit 6bc3f9b). (1) Important — ResolveDrainReplyByteBudget was a step, not a floor: just above the 64 KiB reserve the budget collapsed to a few bytes (exactly 1024 at the validator floor MaxMessageBytes = 1024 + 64 KiB), so a byte-heavy DrainEvents truncated on every call and the drain-until-empty loop never terminated. Now Math.Max(frameMax - reserve, frameMax / 2) — monotonic, never below half the frame max. New test WorkerPipeSessionTests.DrainEvents_AtValidatorFloorFrameMax_MakesProgressAndTerminates drives a byte-heavy queue at the exact validator floor and asserts drain-to-empty with no head reported oversized. (2) Hardening — the reply-too-large fallback write is now itself size-guarded (WriteReplyTooLargeFallbackAsync, shared by the control and STA reply seams) so a pathologically tiny negotiated max below the gateway floor (the WRK-24 gap) cannot make even the backstop session-fatal; log-and-swallow, comment points at WRK-24. (3) Comment — corrected the RepeatedFieldOverheadBytes docs: WorkerEvent.CalculateSize() already includes the event's tag+length, so the 8 bytes is pure slack, not wrapper compensation. Evidence — macOS build 0/0, validator filter 4/4. windev (windev-worker-ci.ps1 -Sha 6bc3f9b -Mode test, 07:07): x86 Worker build 0/0, Worker.Tests 368 passed / 0 failed / 11 skipped, script exit 0. (An earlier run of the same SHA flaked on the pre-existing RunAsync_WhenStaActivityIsStale_WritesWatchdogFault — a 5 s CTS timeout under first-run load, untouched by this change; it passed on the clean re-run and in both prior full runs.) |
| 2026-08-07 |
SEC-36 → Done (repo-side); live rotation operator-pending (branch fix/sec-36-ldap-secret). State reconciliation: the plaintext-in-appsettings.json half was already discharged before this branch — HEAD ships "ServiceAccountPassword": "${secret:ldap/mxgateway/bind}" (a fail-closed encrypted-store reference, documented at GatewayConfiguration.md:252, tested by PreHostSecretExpansionTests), not the literal the spec's stale baseline assumed. Deviation from spec step 2: the ${secret:} reference was kept, not deleted — deleting it would regress the shipped/documented/tested secret-store channel and the finding (committed plaintext) is already resolved for appsettings.json. The live residual leak was the literal value in glauth.md, docs/GatewayTesting.md, and the historical archreview/* SEC-06 evidence — all scrubbed to <service-account-password> placeholders with a source-of-truth pointer (scadaproj/infra/glauth/). Added <UserSecretsId>mxaccessgw-server</UserSecretsId> to the server csproj (dev channel) and extended the ValidateLdap blank-password message to name both channels (dev user-secrets, deployed MxGateway__Ldap__ServiceAccountPassword) — asserted by the updated Validate_Fails_WhenLdapEnabledAndServiceAccountPasswordBlank. Docs same commit: GatewayConfiguration.md (three-channel + rotation note), glauth.md (placeholders + rotation-required note + runbook pointer), docs/GatewayTesting.md (live-LDAP env-var example de-secreted). New operator runbook docs/runbooks/SEC-36-ldap-credential-rotation.md carries the cutover order (generate in glauth → pre-stage NEW env var on 10.100.0.48 and on wonder-app-vd03 only if Ldap.Enabled → rotate GLAuth on 10.100.0.35 → verify dashboard login → devs set user-secrets). The live GLAuth rotation + NSSM staging remain the operator's to execute and are tracked in that runbook. Verification (macOS): dotnet build …Server 0 warnings/0 errors; --filter ~GatewayOptionsValidator green; git grep -i for the old value returns nothing across tracked files. |
| 2026-08-07 |
Worker-seam batch → Done: WRK-22 (mechanics for IPC-26), WRK-24, WRK-25, WRK-27 (branch fix/wrk-22-25-seam). WRK-22/IPC-26: WorkerFrameWriter.PendingFrame gained a Claimed field; a WriteAsync/WriteBatchAsync cancelled while waiting for the write lock tombstones its still-unclaimed frame (TrySetCanceled under _gate) and DequeueNext skips cancelled frames and marks the one it returns Claimed, so a cancelled write never reaches the wire — except the documented, by-design residual where a lock-holder claimed the frame first (mid-write, cannot be recalled; caller still observes cancellation). WRK-25: new WriteBatchAsync(IReadOnlyList<WorkerEnvelope>, priority, ct) enqueues a whole batch under one _gate acquisition, takes the lock once, drains, then observes every completion (surfacing the first per-frame rejection); RunEventDrainLoopAsync now submits the drained event batch through it, so a burst of N events costs one flush not N — the WRK-12 coalescing now engages on the event hot path. IPC-30's oversized-event structured fault is preserved (FindOversizedEvent maps the batch rejection back to the offending event). WRK-24: WorkerFrameProtocolOptions.MinNegotiableFrameBytes = 1024 (matches GatewayOptionsValidator.MinimumMaxMessageBytes); AdoptNegotiatedMaxMessageBytes now faults a below-floor negotiated value at the handshake, closing the [1024, 256 MiB] accepted range. WRK-27: alarm poll runs outside the dispatcher, so MxAccessStaSession sets a volatile staAlarmPollInProgress around the PollOnce COM call and surfaces it on the new WorkerRuntimeHeartbeatSnapshot.StaCallInProgress; ReportWatchdogFaultIfNeededAsync honors it alongside CurrentCommandCorrelationId, so a healthy-but-slow poll gets grace-to-ceiling suppression (not the 15 s grace) but still faults past the 75 s ceiling. Docs same commit: docs/WorkerFrameProtocol.md (accepted-range paragraph, flush-coalescing sentence flipped to coalesced-on-drain, cancellation-tombstone contract replacing the WRK-26 "pending" placeholder), docs/MxAccessWorkerInstanceDesign.md (watchdog alarm-poll paragraph). New tests: WorkerFrameProtocolTests.{WriteAsync_CancelledWhileWaitingForLock_FrameIsNeverWritten, WriteAsync_CancelledEventFrame_DoesNotTrailShutdownAck, WriteBatchAsync_FlushesOnceAndPreservesOrder, WriteBatchAsync_ControlFrameQueuedDuringBatch_JumpsRemainingEvents, AdoptNegotiatedMaxMessageBytes_BelowFloor_ThrowsInvalidConfiguration}, WorkerPipeSessionTests.{Watchdog_StaCallInProgress_SuppressedUntilCeiling, EventBurst_DrainLoopCoalescesFlushes, Handshake_GatewayHelloWithTinyMaxFrameBytes_FaultsAtHandshake}, MxAccessStaSessionTests.CaptureHeartbeat_DuringAlarmPoll_ReportsStaCallInProgress (+ FakeRuntimeSession.EnqueueEvents bulk helper, staCallInProgress snapshot ctor param). IPC-26 → Done (mechanics owned here). Evidence — macOS: dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx 0 warnings / 0 errors (worker excluded there; proves the shared/server side is intact). windev (scripts/ci/windev-worker-ci.ps1 -Sha 8df35cd -Mode test, 2026-08-07 07:45): x86 Worker build 0 warnings / 0 errors, Worker.Tests 377 passed / 0 failed / 11 skipped (skips are the live-MXAccess/dev-rig opt-ins; +9 over the prior 368 = the nine new tests, all green), script exit 0. |
| 2026-08-07 |
P1 doc-drift batch → Done: TST-27, WRK-26 (discharges IPC-29), CLI-42, CLI-43, IPC-28 (branch fix/doc-drift-batch). Doc-only; no source, proto, or test changes — cross-checked against HEAD in this worktree. TST-27: docs/GatewayConfiguration.md's ShowTagValues row no longer says "Reserved" — it now states what false (default) does (DashboardEventBroadcaster blanks tag values from a deep-cloned MxEvent before the SignalR events-hub mirror, metadata still renders), the security relevance (the per-session hub ACL, SEC-25 roadmap item 12, still does not exist, so this redaction is the only thing between a low-trust Viewer and other sessions' tag values), and the honest scope limit (the flag does not cover /browse). WRK-26 (discharges IPC-29): docs/MxAccessWorkerInstanceDesign.md's "Outbound Queues" section rewritten from the stale five-level priority list to the two-class Control/Event scheduler actually shipped (WorkerFrameWriter/WorkerFrameWritePriority.cs), with the collapsed-decision rationale recorded, and the overflow paragraph rewritten to the implemented fail-fast (WorkerFault category QueueOverflow → fault frame written → RunAsync unwinds → generic WorkerExitCode.UnexpectedFailure, dedicated code still open). docs/WorkerFrameProtocol.md gained a new "Write Scheduling And Sequencing" section: the two priority classes, enqueue-then-contend/single-lock-holder-drains-all, write-time peek-stamp-commit sequencing, per-frame-rejection vs. stream-failure semantics, and flush coalescing — stated truthfully as landed (WRK-23's peek-stamp-commit is live at HEAD) or not (the drain loop still awaits each event WriteAsync individually, so WRK-25's N-events-one-flush batching has not landed and the section says so explicitly). Cancellation is deliberately not documented as a firm contract — a one-paragraph placeholder notes it is pending WRK-22, which has not landed (confirmed by reading WorkerFrameWriter.cs: no Claimed/tombstone machinery exists yet). gateway.md:328-330 was cross-checked and left unchanged — its sequence prose (both sides stamp at write, per GWC-28) already reads true. CLI-42: clients/rust/README.md and docs/ClientPackaging.md's Rust section now document the vendored proto layout matching clients/rust/build.rs exactly — repo-path-first resolution (../../src/ZB.MOM.WW.MxGateway.Contracts/Protos) falling back to clients/rust/protos/ when the canonical path is absent (published-tarball case), the same-commit refresh rule enforced by scripts/check-codegen.ps1 Check 3, and why cargo package/cargo publish run without --no-verify (matches scripts/pack-clients.ps1:190-192). CLI-43: docs/style-guides/JavaStyleGuide.md line 8 now says "Target Java 17 (the Ignition 8.3 baseline...)" mirroring the CLI-12 wording, matching the shipped clients/java/build.gradle toolchain-17 build. IPC-28: docs/Grpc.md's exception-mapping prose gained CommandTooLarge → ResourceExhausted (verified against the live switch in Grpc/MxAccessGatewayService.cs:950-960), and the Invoke section gained one sentence on the oversized-payload path (WorkerClient.InvokeAsync rejects at the enqueue boundary per-correlation, session not faulted — verified against WorkerClient.cs:220-234), cross-referencing the headroom rule already documented in docs/GatewayConfiguration.md:120-129. Did not touch the DrainEvents-truncation row or the proto/Generated/ trees — those belong to a parallel codegen task per the handoff note. Source files cross-read for accuracy (no edits): src/ZB.MOM.WW.MxGateway.Worker/Ipc/WorkerFrameWriter.cs, .../WorkerFrameWritePriority.cs, .../WorkerPipeSession.cs (confirmed two-class scheduler, WRK-21/23/28/30 landed, WRK-25/WRK-22 not landed), src/ZB.MOM.WW.MxGateway.Worker/WorkerApplication.cs (exit-code mapping), src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessEventQueue.cs (overflow fault path), src/ZB.MOM.WW.MxGateway.Server/Dashboard/Hubs/DashboardEventBroadcaster.cs + Configuration/DashboardOptions.cs + docs/GatewayDashboardDesign.md:170 (ShowTagValues), src/ZB.MOM.WW.MxGateway.Server/Grpc/MxAccessGatewayService.cs:940-963 + Workers/WorkerClient.cs:205-244 + Workers/WorkerClientErrorCode.cs (CommandTooLarge mapping), clients/rust/build.rs, clients/rust/Cargo.toml, scripts/check-codegen.ps1, scripts/pack-clients.ps1 (Rust vendoring), gateway.md:326-360 (sequence-prose cross-check). Verification (greps, doc-only — no build required): grep -n 'Reserved' docs/GatewayConfiguration.md no longer matches the ShowTagValues row; grep -n 'faults' docs/MxAccessWorkerInstanceDesign.md shows no remaining five-level list; grep -n 'scheduling' docs/WorkerFrameProtocol.md finds the new section; grep -rn 'Java 21' docs/style-guides/ empty; grep -i vendored docs/ClientPackaging.md clients/rust/README.md non-empty in both; grep -n 'CommandTooLarge' docs/Grpc.md shows the mapping. |
| 2026-08-07 |
IPC-23 + IPC-24 + IPC-25 + IPC-32 → Done; GWC-25 deferred proto-comment resolved (branch fix/ipc-24-25-codegen). The proto-comment/doc + regen wave for the P0 codegen-freshness cluster. Proto comments (comment-only): mxaccess_worker.proto GatewayHello.max_frame_bytes gained the every-frame-must-fit / reply-builders-truncate sentence (IPC-23); mxaccess_gateway.proto DrainEventsReply gained the count-cap+byte-cap / drain-until-empty comment (IPC-23); mxaccess_gateway.proto ReplayGap.oldest_available_sequence gained the empty-ring value definition (highest observed + 1, oldest − 1 formula stays valid) resolving GWC-25's deferred amendment. Regen wave: Contracts/Generated/{MxaccessGateway,MxaccessWorker}.cs (XML-doc only, no descriptor delta), clients/rust/protos/{mxaccess_gateway,mxaccess_worker}.proto byte-copied, Go internal/generated/{mxaccess_gateway,mxaccess_worker}.pb.go (worker binding was genuinely stale — it lacked the whole MaxFrameBytes field/accessor/rawDesc, now refreshed; gateway.pb.go got the new doc comments), Python generated/mxaccess_worker_pb2.py (real descriptor delta — max_frame_bytes field + cascaded offsets), Java aggregates MxaccessGateway.java/MxaccessWorker.java (javadoc from the new proto comments — zero protobuf-version churn under the pinned toolchain), and the client descriptor set mxaccessgw-client-v1.protoset. IPC-24: regenerating Java on the pinned toolchain (grpc 1.76.0 / protobuf 4.33.1, Homebrew JDK 17) produced only the legitimate javadoc delta with no validateProtobufGencodeVersion/major=/minor=/patch= churn — so the preferred path was taken: the unconditional churn-revert step and its comment were deleted from .gitea/workflows/ci.yml (the git diff --exit-code gate is now a true message-level drift gate for the single-file Java aggregates). IPC-25: pinned the Go generators in clients/go/generate-proto.ps1 (protoc-gen-go v1.36.11 assert, protoc-gen-go-grpc 1.6.2 assert, protoc warn-only; also fixed a latent (if …)→$(if …) parse bug that broke the script under pwsh 7); added Check 4 to scripts/check-codegen.ps1 (regenerate Go+Python bindings, fail on any diff, tool-missing FAILS not skips) and relabeled the banners 1/4…4/4 + header comment (IPC-32 folded in); added the pinned-generator installs to the portable CI job before the codegen step. Docs same commit: ClientProtoGeneration.md (pinned-versions table + Go/Python Check-4 guard + Java-revert-deleted note), Contracts.md (four-check enumeration), GatewayTesting.md (java job + four-check prose), clients/java/zb-mom-ww-mxgateway-client/build.gradle checkGeneratedClean caveat. IPC-23 DrainEvents-truncation doc deviation: docs/Grpc.md has no DrainEvents row (it documents only the seven public RPCs; DrainEvents is a worker diagnostic), and docs/WorkerFrameProtocol.md (owned by the parallel doc batch) already carries the byte-cap/drain-until-empty prose via WRK-21 — so no Grpc.md edit was made. Evidence (macOS): pwsh scripts/check-codegen.ps1 all four checks green (banners 1/4…4/4); dotnet build …NonWindows.slnx 0 warnings/0 errors; dotnet test …Tests --filter ~ClientProtoInputTests green; grep -c max_frame_bytes → Go worker 2, Python worker 1 (both non-zero, were 0); clients/go gofmt clean + go build + go test ok; clients/python pytest 163 passed/1 skipped; Java gradle generateProto clean under JDK 17. Negative-path proof: added a scratch field to mxaccess_worker.proto, regenerated only Contracts/Generated/, reran check-codegen.ps1 and confirmed Check 4 fails naming both clients/go/internal/generated and clients/python/…/generated, then reverted. Not published (CLI-39 gates Go/Python republish). |
| 2026-08-07 |
IPC-27 → Done (branch fix/ipc-27-descriptor-test). ClientProtoInputTests.Descriptor_ContainsEveryContractMessageAndField renamed to Descriptor_ContainsEveryContractSymbol and extended: the reflection walk now also collects enums (top-level file.EnumTypes + nested message.EnumTypes, as {enumFullName} and {enumFullName}/{valueName}) and services/methods (file.Services, as {serviceFullName} and {serviceFullName}/{methodName}), and GalaxyRepositoryReflection.Descriptor was added to the enumerated contract files alongside the existing gateway/worker descriptors. The published-side CollectPublishedSymbols grew matching walks over FileDescriptorProto.EnumType/Service and DescriptorProto.EnumType; the comparison stays a flat, order-insensitive string-set diff with no protoc dependency. Docs: docs/ClientProtoGeneration.md and docs/Contracts.md updated from "message or field" to full symbol coverage (message/field/enum/enum value/service/method), both now naming galaxy_repository.proto explicitly. Red-path proof performed: pointed the test at the pre-IPC-01 stale protoset (git cat-file -p 0f88a95:clients/proto/descriptors/mxaccessgw-client-v1.protoset), confirmed it failed naming — among the missing symbols — mxaccess_worker.v1.GatewayHello/max_frame_bytes, enum values (MxCommandKind/MX_COMMAND_KIND_SUBSCRIBE_ALARMS, AlarmProviderMode/*, MxEventFamily/MX_EVENT_FAMILY_ON_ALARM_PROVIDER_MODE_CHANGED), services/methods (MxAccessGateway/StreamAlarms, GalaxyRepository/BrowseChildren), and the full galaxy_repository.v1.* surface; restored the real descriptor path afterward and re-ran green. Verification: dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx 0 warnings/0 errors; dotnet test …Tests --filter FullyQualifiedName~ClientProtoInputTests 5/5 passed. No proto, config, or CI change, matching the design's stated scope. |
| 2026-08-07 |
TST-30 → Done (doc half); runner registration operator-pending (branch fix/tst-30-runner-docs). Doc + operator-runbook task only — no source/tests, per the finding's P2 scope. docs/GatewayTesting.md's Continuous Integration section gained a "Runner capacity is shared and finite" subsection: the co-located gitea-runner on 10.100.0.35 runs maxParallel=1 and is registered at the Gitea instance level (GET /repos/dohertj2/mxaccessgw/actions/runners → total_count: 0), so it interleaves with dohertj2/lmxopcua and every job in a run serializes — ~20–30 minute queue depth was observed under cross-repo contention during TST-25 acceptance. Documented that Gitea 1.26 exposes no run cancel/delete (POST .../cancel → 404, DELETE → 400), so a superseded or hung run holds its slot to completion. The existing "windev tier down" degraded-mode paragraph now also names "runner contended" as a reason to use the bypass — `CI_SHA= scripts/ci/run-windev-ci.sh <build |