From eacdd2d4538f701a78d70004e852ddfb3ca66f33 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 7 Aug 2026 07:32:22 -0400 Subject: [PATCH] fix(IPC-23,IPC-24,IPC-25,IPC-32): proto-comment regen wave + codegen-freshness guards Proto comments (comment-only, no wire change): - mxaccess_worker.proto GatewayHello.max_frame_bytes: every worker->gateway frame must serialize within the negotiated max; reply builders truncate (IPC-23). - mxaccess_gateway.proto DrainEventsReply: count-cap + byte-cap, drain-until-empty caller contract (IPC-23). - mxaccess_gateway.proto ReplayGap.oldest_available_sequence: empty-ring value is highest-observed+1, oldest-1 resume formula stays valid (GWC-25 deferred amendment). Regen wave: Contracts/Generated (C# XML doc), rust vendored protos (byte-copy), Go bindings (worker binding was genuinely stale - lacked MaxFrameBytes entirely), Python worker _pb2 (real descriptor delta), Java aggregates (javadoc, zero protobuf-version churn under the pinned toolchain), client descriptor set. IPC-24: pinned Java toolchain regenerates with no gencode-version churn, so the unconditional churn-revert step in ci.yml is a fossil - deleted it; git diff is now a true message-level drift gate for the single-file Java aggregates. IPC-25: pin protoc-gen-go v1.36.11 / protoc-gen-go-grpc 1.6.2 in the Go generate script (+ fix a latent pwsh-7 parse bug); add Check 4 to check-codegen.ps1 (regenerate Go+Python bindings, fail on diff, tool-missing fails not skips); add the pinned-generator installs to the portable CI job. IPC-32: relabel check-codegen banners 1/4..4/4 (folded into the Check 4 edit). Docs: ClientProtoGeneration.md, Contracts.md, GatewayTesting.md, build.gradle checkGeneratedClean caveat. Tracking: IPC-23/24/25/32 -> Done, GWC-25 proto note resolved, change-log 2026-08-07. --- .gitea/workflows/ci.yml | 33 ++- .../2026-07-12/remediation/00-tracking.md | 9 +- .../2026-07-12/remediation/10-gateway-core.md | 2 +- .../remediation/30-contracts-ipc.md | 8 +- clients/go/generate-proto.ps1 | 31 ++- .../internal/generated/mxaccess_gateway.pb.go | 13 +- .../internal/generated/mxaccess_worker.pb.go | 25 +- .../mxaccess_gateway/v1/MxaccessGateway.java | 221 ++++++++++++++++++ .../mxaccess_worker/v1/MxaccessWorker.java | 15 ++ .../zb-mom-ww-mxgateway-client/build.gradle | 15 +- .../descriptors/mxaccessgw-client-v1.protoset | Bin 117332 -> 118154 bytes .../generated/mxaccess_worker_pb2.py | 52 ++--- clients/rust/protos/mxaccess_gateway.proto | 9 + clients/rust/protos/mxaccess_worker.proto | 3 + docs/ClientProtoGeneration.md | 22 +- docs/Contracts.md | 6 +- docs/GatewayTesting.md | 23 +- scripts/check-codegen.ps1 | 38 ++- .../Generated/MxaccessGateway.cs | 11 + .../Generated/MxaccessWorker.cs | 3 + .../Protos/mxaccess_gateway.proto | 9 + .../Protos/mxaccess_worker.proto | 3 + 22 files changed, 469 insertions(+), 82 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index a644bbe..072b06f 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -60,7 +60,19 @@ jobs: dotnet tool install --global PowerShell echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH" - # IPC-01 / IPC-19 / IPC-20: descriptor set + Contracts/Generated must match the current protos. + # IPC-25 Check 4 regenerates the Go and Python client bindings and diffs them, so the pinned + # generators must be present. protoc 34.1 is already installed above; Go and Python are set up + # above. Pin protoc-gen-go / protoc-gen-go-grpc to match the committed header stamps and grpcio + # -tools to match the committed _pb2 stamp, or Check 4 false-fails (or masks drift) under churn. + - name: Install pinned client codegen generators (Check 4) + run: | + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11 + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.2 + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + python -m pip install 'grpcio-tools==1.80.0' + + # IPC-01 / IPC-19 / IPC-20 / IPC-25: descriptor set + Contracts/Generated + Go/Python bindings + # must match the current protos. - name: Codegen / descriptor freshness shell: pwsh run: ./scripts/check-codegen.ps1 @@ -93,10 +105,12 @@ jobs: python -m pytest java: - # Java client runs on a JDK-17 Linux runner (the macOS dev box has no JRE). The protobuf gradle - # plugin rewrites MxaccessGateway.java with spurious protobuf-runtime-version churn on every - # build; when no .proto changed, revert that one file so checkGeneratedClean / a dirty tree does - # not fail the build (repo memory project_java_generated_churn). + # Java client runs on a JDK-17 Linux runner (the macOS dev box has no JRE). The grpc/protobuf + # toolchain is fully pinned (clients/java/build.gradle: grpcVersion 1.76.0 / protobufVersion + # 4.33.1), so a regeneration is byte-identical to the committed aggregates modulo real .proto + # changes — `Verify generated tree is clean` (git diff) is the true drift gate (IPC-24). The + # single-file Java aggregates are where message-level proto drift lands, so this job now catches + # a .proto edited without regenerating and committing the Java client. runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -115,13 +129,10 @@ jobs: - name: Gradle test working-directory: clients/java run: gradle test - - name: Revert spurious protobuf-version churn (no .proto changed) - # Both generated aggregates can pick up protobuf-runtime-version churn on regen; revert - # both so verify-clean still catches a real, uncommitted proto/codegen change elsewhere. - run: | - git checkout -- clients/java/src/main/generated/main/java/mxaccess_gateway/v1/MxaccessGateway.java || true - git checkout -- clients/java/src/main/generated/main/java/mxaccess_worker/v1/MxaccessWorker.java || true - name: Verify generated tree is clean + # IPC-24: the pinned grpc/protobuf toolchain regenerates byte-identical output, so this + # git-diff gate now catches message-level proto drift in the single-file Java aggregates + # (the old unconditional churn-revert step masked exactly that class and was deleted). run: git diff --exit-code -- clients/java/src/main/generated windows-x86: diff --git a/archreview/2026-07-12/remediation/00-tracking.md b/archreview/2026-07-12/remediation/00-tracking.md index a2619a4..15cd69e 100644 --- a/archreview/2026-07-12/remediation/00-tracking.md +++ b/archreview/2026-07-12/remediation/00-tracking.md @@ -44,12 +44,12 @@ Sequenced by cluster; a cluster is one change set. | CLI-35 | Medium | S | GWC-25 (coord) | Done | Python CLI `stream-events` crashes on a ReplayGap | | CLI-36 | Medium | S | GWC-25 (coord) | Done | Go CLI `stream-events` silently destroys the ReplayGap signal | | WRK-21 | Medium | M | owns IPC-23 fix; WRK-28 same batch | Done | DrainEvents bound is count-based only; oversized reply kills the session and loses the drained events | -| IPC-23 | Medium | S | WRK-21 | In progress — mechanics landed with WRK-21; proto-comment/doc wave pending | DrainEvents contract requirements (reply fits negotiated max, no event loss, drain-until-empty) + proto-comment/doc wave | +| IPC-23 | Medium | S | WRK-21 | Done | DrainEvents contract requirements (reply fits negotiated max, no event loss, drain-until-empty) + proto-comment/doc wave | | IPC-30 | Low | M | WRK-21 (same batch) | Done | Oversized event frame stays session-fatal by design, but the death becomes structured (fault frame + logged identity) | | SEC-31 | Medium | M | — | Done | Failure limiter partitions on attacker-controlled key id and blocks before verification (lockout DoS) | | SEC-32 | Low | S | SEC-31 | Done | Failure-limiter LRU flushable by junk-token spray; token prefix never validated | -| IPC-24 | Medium | S | — | Not started | CI's unconditional Java churn-revert masks real drift | -| IPC-25 | Medium | M | — | Not started | Regenerate stale Go/Python worker bindings + add binding-freshness guard (Check 4) to check-codegen.ps1 | +| IPC-24 | Medium | S | — | Done | CI's unconditional Java churn-revert masks real drift | +| IPC-25 | Medium | M | — | Done | Regenerate stale Go/Python worker bindings + add binding-freshness guard (Check 4) to check-codegen.ps1 | ## Finding registers by domain @@ -93,7 +93,7 @@ Full design + implementation for each row lives in the linked domain doc under i | IPC-29 | Low | — | S | WRK-26 (discharged by) | Not started | WorkerFrameProtocol.md missing write-scheduling/sequencing section | | IPC-30 | Low | P0 | M | WRK-21 (same batch) | Done | Oversized event frame: keep session-fatal, make the death structured | | IPC-31 | Info | — | — | — | N/A | Gateway creation-time sequence stamping accepted; diagnostic-only, decision recorded | -| IPC-32 | Info | — | S | IPC-25 (folded in) | Not started | check-codegen banner relabel 1/4…4/4 | +| IPC-32 | Info | — | S | IPC-25 (folded in) | Done | check-codegen banner relabel 1/4…4/4 | ### Security & dashboard — [40-security-dashboard.md](40-security-dashboard.md) @@ -174,3 +174,4 @@ Sequence these together rather than piecemeal — several are one change set spa | 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 | **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). | diff --git a/archreview/2026-07-12/remediation/10-gateway-core.md b/archreview/2026-07-12/remediation/10-gateway-core.md index f35710c..5a04430 100644 --- a/archreview/2026-07-12/remediation/10-gateway-core.md +++ b/archreview/2026-07-12/remediation/10-gateway-core.md @@ -61,7 +61,7 @@ Coordinate with (do not block on) open GWC-21: if `EventChannelFullModeTimeout` The proto comment currently states "`oldest_available_sequence` itself IS still retained", which becomes false in the empty-ring case — per the docs-with-source rule, amend the field comment in the same commit to define the empty-ring value ("when nothing is retained, this is the next sequence that can be delivered — `highest observed + 1` — and the `oldest − 1` resume formula remains valid; the interval evicted is unchanged"). This is a comment-only proto change (no descriptor delta), but the repo's codegen rules still apply — see the steps. -**Implementation.** +**Implementation.** (Code + `docs/Sessions.md` landed 2026-08-07 on `fix/gwc-25-replaygap-trio`; the deferred proto-comment amendment below **landed 2026-08-07** with the IPC-23 codegen wave on `fix/ipc-24-25-codegen` — GWC-25 is fully resolved.) - `Sessions/SessionEventDistributor.cs:463-467`: replace `oldestAvailableSequence = 0;` with `oldestAvailableSequence = gap ? _highestSequenceSeen + 1 : 0;` plus a comment explaining the `oldest − 1` client formula this must keep valid (cite this finding). - `src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto` (`ReplayGap.oldest_available_sequence`, ~line 759): append the empty-ring sentence above. Then regenerate per repo rules: delete `src/ZB.MOM.WW.MxGateway.Contracts/Generated/*.cs`, `dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj`, and **commit `Generated/`** (net48 worker builds break otherwise). Sync the vendored client copies of the proto byte-identical (`clients/*/`); a comment-only edit changes no descriptor, so: Python `*_pb2*` output is unchanged (comments are not embedded — regenerate with the pinned grpcio-tools only if the files actually differ), Go/C#/Rust generated doc comments will churn — regenerate those per each client README, and revert spurious Java aggregate-file churn if no message-level delta appears (per the established Java convention). - `docs/Sessions.md` (~lines 228-234, ReplayGap section): document the empty-ring sentinel value and that `after_worker_sequence = oldest_available_sequence − 1` is the universal resume formula in both the retained and fully-evicted cases. diff --git a/archreview/2026-07-12/remediation/30-contracts-ipc.md b/archreview/2026-07-12/remediation/30-contracts-ipc.md index e495d42..c797f87 100644 --- a/archreview/2026-07-12/remediation/30-contracts-ipc.md +++ b/archreview/2026-07-12/remediation/30-contracts-ipc.md @@ -12,16 +12,16 @@ All `path:line` citations were re-verified against the working tree at `4f5371f` | ID | Sev | Tier | Eff | Dep | Status | Title | |----|-----|------|-----|-----|--------|-------| -| IPC-23 | Medium | P0 | S¹ | WRK-21 | In progress — mechanics landed with WRK-21; proto-comment/doc wave pending | DrainEvents bound is count-based only; byte-heavy queue still builds a session-killing reply frame (contract requirements here; fix mechanics in WRK-21) | -| IPC-24 | Medium | P0 | S | — | Not started | CI's unconditional Java churn-revert masks real generated-code drift for message-level proto changes | -| IPC-25 | Medium | P0 | M | — | Not started | Committed Go/Python worker bindings are stale at HEAD; no guard covers them | +| IPC-23 | Medium | P0 | S¹ | WRK-21 | Done | DrainEvents bound is count-based only; byte-heavy queue still builds a session-killing reply frame (contract requirements here; fix mechanics in WRK-21) | +| IPC-24 | Medium | P0 | S | — | Done | CI's unconditional Java churn-revert masks real generated-code drift for message-level proto changes | +| IPC-25 | Medium | P0 | M | — | Done | Committed Go/Python worker bindings are stale at HEAD; no guard covers them | | IPC-26 | Low | P2 | S¹ | WRK-22 | Not started | Cancelled write leaves a ghost frame that is still written (contract requirement here; fix mechanics in WRK-22) | | IPC-27 | Low | P2 | S | — | Not started | Descriptor freshness test blind to enums, enum values, services/methods, and the Galaxy contract | | IPC-28 | Low | — | S | — | Not started | `docs/Grpc.md` omits the `CommandTooLarge` → `ResourceExhausted` mapping | | IPC-29 | Low | — | S | — | Not started | Worker writer priority scheduling and write-time sequence stamping undocumented in the frame-protocol doc | | IPC-30 | Low | P0 | M | WRK-21 (same file/batch) | Done | Oversized worker→gateway event frame is session-fatal — make the death deliberate, structured, and diagnosable | | IPC-31 | Info | — | — | — | N/A | Gateway stamps sequence at creation, worker at write — accepted divergence; sequence is documented diagnostic-only (`gateway.md:328-330`); revisit only if sequence ever becomes load-bearing | -| IPC-32 | Info | — | S | IPC-25 | Not started | `check-codegen.ps1` check labels miscounted (folded into the IPC-25 script edit) | +| IPC-32 | Info | — | S | IPC-25 | Done | `check-codegen.ps1` check labels miscounted (folded into the IPC-25 script edit) | ¹ Effort for the work owned by *this* plan (proto comments + docs + acceptance criteria). The code mechanics are M and are tracked under WRK-21 / WRK-22 in the worker plan. diff --git a/clients/go/generate-proto.ps1 b/clients/go/generate-proto.ps1 index 9c807e6..85489b0 100644 --- a/clients/go/generate-proto.ps1 +++ b/clients/go/generate-proto.ps1 @@ -1,6 +1,16 @@ Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' +# Pinned generator baseline. The committed Go bindings stamp these plugin versions in their +# headers (protoc-gen-go v1.36.11 / protoc-gen-go-grpc v1.6.2). Plugin-version drift rewrites +# those header stamps, so a regeneration on an off-pin machine would churn the tree and make +# check-codegen Check 4 false-fail (or mask real drift under churn). Assert the exact versions +# so a regen is deterministic. protoc itself is warn-only (source_code_info is normalized out of +# the committed bindings), matching publish-client-proto-inputs.ps1. +$PinnedProtocGenGoVersion = 'protoc-gen-go v1.36.11' +$PinnedProtocGenGoGrpcVersion = 'protoc-gen-go-grpc 1.6.2' +$PinnedProtocVersion = 'libprotoc 34.1' + $repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..') $protoRoot = Join-Path $repoRoot 'src\ZB.MOM.WW.MxGateway.Contracts\Protos' $outputRoot = Join-Path $PSScriptRoot 'internal\generated' @@ -36,8 +46,25 @@ $wingetProtoc = if ($env:LOCALAPPDATA) { $goBin = if ($env:USERPROFILE) { Join-Path $env:USERPROFILE 'go\bin' } elseif ($env:HOME) { Join-Path $env:HOME 'go/bin' } else { $null } $protoc = Resolve-Tool -Names @('protoc', 'protoc.exe') -FallbackPaths @($wingetProtoc) -$protocGenGo = Resolve-Tool -Names @('protoc-gen-go', 'protoc-gen-go.exe') -FallbackPaths @((if ($goBin) { Join-Path $goBin 'protoc-gen-go.exe' }), (if ($goBin) { Join-Path $goBin 'protoc-gen-go' })) -$protocGenGoGrpc = Resolve-Tool -Names @('protoc-gen-go-grpc', 'protoc-gen-go-grpc.exe') -FallbackPaths @((if ($goBin) { Join-Path $goBin 'protoc-gen-go-grpc.exe' }), (if ($goBin) { Join-Path $goBin 'protoc-gen-go-grpc' })) +$protocGenGo = Resolve-Tool -Names @('protoc-gen-go', 'protoc-gen-go.exe') -FallbackPaths @(($(if ($goBin) { Join-Path $goBin 'protoc-gen-go.exe' })), ($(if ($goBin) { Join-Path $goBin 'protoc-gen-go' }))) +$protocGenGoGrpc = Resolve-Tool -Names @('protoc-gen-go-grpc', 'protoc-gen-go-grpc.exe') -FallbackPaths @(($(if ($goBin) { Join-Path $goBin 'protoc-gen-go-grpc.exe' })), ($(if ($goBin) { Join-Path $goBin 'protoc-gen-go-grpc' }))) + +# Assert the pinned plugin versions before generating so Check 4 cannot false-fail (or mask drift) +# on an off-pin machine. protoc is warn-only. +$protocGenGoVersion = (& $protocGenGo --version 2>&1 | Out-String).Trim() +if ($protocGenGoVersion -ne $PinnedProtocGenGoVersion) { + throw "protoc-gen-go reports '$protocGenGoVersion', but regeneration is pinned to '$PinnedProtocGenGoVersion'. " + + "Install the pin: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11" +} +$protocGenGoGrpcVersion = (& $protocGenGoGrpc --version 2>&1 | Out-String).Trim() +if ($protocGenGoGrpcVersion -ne $PinnedProtocGenGoGrpcVersion) { + throw "protoc-gen-go-grpc reports '$protocGenGoGrpcVersion', but regeneration is pinned to '$PinnedProtocGenGoGrpcVersion'. " + + "Install the pin: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.2" +} +$protocVersion = (& $protoc --version 2>&1 | Out-String).Trim() +if ($protocVersion -ne $PinnedProtocVersion) { + Write-Warning "protoc reports '$protocVersion', pin is '$PinnedProtocVersion'. Descriptor comments are normalized out of the committed Go bindings, so patch drift is tolerated; keep CI on the pin." +} # protoc discovers the plugins on PATH; prepend the directories the resolved plugins live in. $env:Path = (Split-Path $protocGenGo -Parent) + [System.IO.Path]::PathSeparator + (Split-Path $protocGenGoGrpc -Parent) + [System.IO.Path]::PathSeparator + $env:Path diff --git a/clients/go/internal/generated/mxaccess_gateway.pb.go b/clients/go/internal/generated/mxaccess_gateway.pb.go index 2932241..d3733d6 100644 --- a/clients/go/internal/generated/mxaccess_gateway.pb.go +++ b/clients/go/internal/generated/mxaccess_gateway.pb.go @@ -5974,8 +5974,12 @@ func (x *WorkerInfoReply) GetMxaccessClsid() string { } type DrainEventsReply struct { - state protoimpl.MessageState `protogen:"open.v1"` - Events []*MxEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // The reply is bounded by both a server-side count cap and the negotiated + // worker-frame byte cap; a reply may therefore carry fewer events than + // `max_events` and fewer than are queued. Callers drain iteratively until an + // empty reply. + Events []*MxEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -6411,6 +6415,11 @@ type ReplayGap struct { // after_worker_sequence = oldest_available_sequence - 1 in the next // StreamEventsRequest, which will cause the server to replay starting at // oldest_available_sequence (the first retained event). + // When nothing is retained (the replay ring is empty), this is the next sequence + // that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + // formula remains valid: it resolves to the highest sequence already seen, so the + // follow-up resume replays nothing, reports no gap, and every newer live event + // passes. The interval evicted is unchanged. OldestAvailableSequence uint64 `protobuf:"varint,2,opt,name=oldest_available_sequence,json=oldestAvailableSequence,proto3" json:"oldest_available_sequence,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/clients/go/internal/generated/mxaccess_worker.pb.go b/clients/go/internal/generated/mxaccess_worker.pb.go index 1ecea8a..1bdfe6e 100644 --- a/clients/go/internal/generated/mxaccess_worker.pb.go +++ b/clients/go/internal/generated/mxaccess_worker.pb.go @@ -431,8 +431,17 @@ type GatewayHello struct { SupportedProtocolVersion uint32 `protobuf:"varint,1,opt,name=supported_protocol_version,json=supportedProtocolVersion,proto3" json:"supported_protocol_version,omitempty"` Nonce string `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` GatewayVersion string `protobuf:"bytes,3,opt,name=gateway_version,json=gatewayVersion,proto3" json:"gateway_version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Maximum worker-frame payload size, in bytes, negotiated by the gateway from its + // configured pipe limit. The worker adopts this as its frame-protocol MaxMessageBytes + // instead of a hard-coded default; 0 (an older gateway that never set the field) means + // "use the worker's built-in default". Sits above the public gRPC cap by an + // envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + // Every worker->gateway frame — events, heartbeats, faults, and control replies + // including DrainEvents — must serialize within this limit; reply builders truncate + // to fit rather than emit an oversized frame. + MaxFrameBytes uint32 `protobuf:"varint,4,opt,name=max_frame_bytes,json=maxFrameBytes,proto3" json:"max_frame_bytes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GatewayHello) Reset() { @@ -486,6 +495,13 @@ func (x *GatewayHello) GetGatewayVersion() string { return "" } +func (x *GatewayHello) GetMaxFrameBytes() uint32 { + if x != nil { + return x.MaxFrameBytes + } + return 0 +} + type WorkerHello struct { state protoimpl.MessageState `protogen:"open.v1"` ProtocolVersion uint32 `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` @@ -1109,11 +1125,12 @@ const file_mxaccess_worker_proto_rawDesc = "" + "\fworker_event\x18\x12 \x01(\v2\x1f.mxaccess_worker.v1.WorkerEventH\x00R\vworkerEvent\x12P\n" + "\x10worker_heartbeat\x18\x13 \x01(\v2#.mxaccess_worker.v1.WorkerHeartbeatH\x00R\x0fworkerHeartbeat\x12D\n" + "\fworker_fault\x18\x14 \x01(\v2\x1f.mxaccess_worker.v1.WorkerFaultH\x00R\vworkerFaultB\x06\n" + - "\x04body\"\x8b\x01\n" + + "\x04body\"\xb3\x01\n" + "\fGatewayHello\x12<\n" + "\x1asupported_protocol_version\x18\x01 \x01(\rR\x18supportedProtocolVersion\x12\x14\n" + "\x05nonce\x18\x02 \x01(\tR\x05nonce\x12'\n" + - "\x0fgateway_version\x18\x03 \x01(\tR\x0egatewayVersion\"\xa1\x01\n" + + "\x0fgateway_version\x18\x03 \x01(\tR\x0egatewayVersion\x12&\n" + + "\x0fmax_frame_bytes\x18\x04 \x01(\rR\rmaxFrameBytes\"\xa1\x01\n" + "\vWorkerHello\x12)\n" + "\x10protocol_version\x18\x01 \x01(\rR\x0fprotocolVersion\x12\x14\n" + "\x05nonce\x18\x02 \x01(\tR\x05nonce\x12*\n" + diff --git a/clients/java/src/main/generated/main/java/mxaccess_gateway/v1/MxaccessGateway.java b/clients/java/src/main/generated/main/java/mxaccess_gateway/v1/MxaccessGateway.java index bfee30d..7271063 100644 --- a/clients/java/src/main/generated/main/java/mxaccess_gateway/v1/MxaccessGateway.java +++ b/clients/java/src/main/generated/main/java/mxaccess_gateway/v1/MxaccessGateway.java @@ -69110,24 +69110,59 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { com.google.protobuf.MessageOrBuilder { /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ java.util.List getEventsList(); /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ mxaccess_gateway.v1.MxaccessGateway.MxEvent getEvents(int index); /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ int getEventsCount(); /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ java.util.List getEventsOrBuilderList(); /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ mxaccess_gateway.v1.MxaccessGateway.MxEventOrBuilder getEventsOrBuilder( @@ -69175,6 +69210,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { @SuppressWarnings("serial") private java.util.List events_; /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ @java.lang.Override @@ -69182,6 +69224,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return events_; } /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ @java.lang.Override @@ -69190,6 +69239,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return events_; } /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ @java.lang.Override @@ -69197,6 +69253,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return events_.size(); } /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ @java.lang.Override @@ -69204,6 +69267,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return events_.get(index); } /** + *
+     * The reply is bounded by both a server-side count cap and the negotiated
+     * worker-frame byte cap; a reply may therefore carry fewer events than
+     * `max_events` and fewer than are queued. Callers drain iteratively until an
+     * empty reply.
+     * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ @java.lang.Override @@ -69567,6 +69637,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { mxaccess_gateway.v1.MxaccessGateway.MxEvent, mxaccess_gateway.v1.MxaccessGateway.MxEvent.Builder, mxaccess_gateway.v1.MxaccessGateway.MxEventOrBuilder> eventsBuilder_; /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public java.util.List getEventsList() { @@ -69577,6 +69654,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { } } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public int getEventsCount() { @@ -69587,6 +69671,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { } } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public mxaccess_gateway.v1.MxaccessGateway.MxEvent getEvents(int index) { @@ -69597,6 +69688,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { } } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder setEvents( @@ -69614,6 +69712,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder setEvents( @@ -69628,6 +69733,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder addEvents(mxaccess_gateway.v1.MxaccessGateway.MxEvent value) { @@ -69644,6 +69756,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder addEvents( @@ -69661,6 +69780,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder addEvents( @@ -69675,6 +69801,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder addEvents( @@ -69689,6 +69822,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder addAllEvents( @@ -69704,6 +69844,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder clearEvents() { @@ -69717,6 +69864,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public Builder removeEvents(int index) { @@ -69730,6 +69884,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return this; } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public mxaccess_gateway.v1.MxaccessGateway.MxEvent.Builder getEventsBuilder( @@ -69737,6 +69898,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { return internalGetEventsFieldBuilder().getBuilder(index); } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public mxaccess_gateway.v1.MxaccessGateway.MxEventOrBuilder getEventsOrBuilder( @@ -69747,6 +69915,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { } } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public java.util.List @@ -69758,6 +69933,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { } } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public mxaccess_gateway.v1.MxaccessGateway.MxEvent.Builder addEventsBuilder() { @@ -69765,6 +69947,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { mxaccess_gateway.v1.MxaccessGateway.MxEvent.getDefaultInstance()); } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public mxaccess_gateway.v1.MxaccessGateway.MxEvent.Builder addEventsBuilder( @@ -69773,6 +69962,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { index, mxaccess_gateway.v1.MxaccessGateway.MxEvent.getDefaultInstance()); } /** + *
+       * The reply is bounded by both a server-side count cap and the negotiated
+       * worker-frame byte cap; a reply may therefore carry fewer events than
+       * `max_events` and fewer than are queued. Callers drain iteratively until an
+       * empty reply.
+       * 
+ * * repeated .mxaccess_gateway.v1.MxEvent events = 1; */ public java.util.List @@ -75305,6 +75501,11 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { * after_worker_sequence = oldest_available_sequence - 1 in the next * StreamEventsRequest, which will cause the server to replay starting at * oldest_available_sequence (the first retained event). + * When nothing is retained (the replay ring is empty), this is the next sequence + * that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + * formula remains valid: it resolves to the highest sequence already seen, so the + * follow-up resume replays nothing, reports no gap, and every newer live event + * passes. The interval evicted is unchanged. * * * uint64 oldest_available_sequence = 2; @@ -75386,6 +75587,11 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { * after_worker_sequence = oldest_available_sequence - 1 in the next * StreamEventsRequest, which will cause the server to replay starting at * oldest_available_sequence (the first retained event). + * When nothing is retained (the replay ring is empty), this is the next sequence + * that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + * formula remains valid: it resolves to the highest sequence already seen, so the + * follow-up resume replays nothing, reports no gap, and every newer live event + * passes. The interval evicted is unchanged. * * * uint64 oldest_available_sequence = 2; @@ -75781,6 +75987,11 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { * after_worker_sequence = oldest_available_sequence - 1 in the next * StreamEventsRequest, which will cause the server to replay starting at * oldest_available_sequence (the first retained event). + * When nothing is retained (the replay ring is empty), this is the next sequence + * that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + * formula remains valid: it resolves to the highest sequence already seen, so the + * follow-up resume replays nothing, reports no gap, and every newer live event + * passes. The interval evicted is unchanged. * * * uint64 oldest_available_sequence = 2; @@ -75800,6 +76011,11 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { * after_worker_sequence = oldest_available_sequence - 1 in the next * StreamEventsRequest, which will cause the server to replay starting at * oldest_available_sequence (the first retained event). + * When nothing is retained (the replay ring is empty), this is the next sequence + * that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + * formula remains valid: it resolves to the highest sequence already seen, so the + * follow-up resume replays nothing, reports no gap, and every newer live event + * passes. The interval evicted is unchanged. * * * uint64 oldest_available_sequence = 2; @@ -75823,6 +76039,11 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile { * after_worker_sequence = oldest_available_sequence - 1 in the next * StreamEventsRequest, which will cause the server to replay starting at * oldest_available_sequence (the first retained event). + * When nothing is retained (the replay ring is empty), this is the next sequence + * that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + * formula remains valid: it resolves to the highest sequence already seen, so the + * follow-up resume replays nothing, reports no gap, and every newer live event + * passes. The interval evicted is unchanged. * * * uint64 oldest_available_sequence = 2; diff --git a/clients/java/src/main/generated/main/java/mxaccess_worker/v1/MxaccessWorker.java b/clients/java/src/main/generated/main/java/mxaccess_worker/v1/MxaccessWorker.java index 3303cfc..5b3819b 100644 --- a/clients/java/src/main/generated/main/java/mxaccess_worker/v1/MxaccessWorker.java +++ b/clients/java/src/main/generated/main/java/mxaccess_worker/v1/MxaccessWorker.java @@ -3797,6 +3797,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile { * instead of a hard-coded default; 0 (an older gateway that never set the field) means * "use the worker's built-in default". Sits above the public gRPC cap by an * envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + * Every worker->gateway frame — events, heartbeats, faults, and control replies + * including DrainEvents — must serialize within this limit; reply builders truncate + * to fit rather than emit an oversized frame. * * * uint32 max_frame_bytes = 4; @@ -3941,6 +3944,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile { * instead of a hard-coded default; 0 (an older gateway that never set the field) means * "use the worker's built-in default". Sits above the public gRPC cap by an * envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + * Every worker->gateway frame — events, heartbeats, faults, and control replies + * including DrainEvents — must serialize within this limit; reply builders truncate + * to fit rather than emit an oversized frame. * * * uint32 max_frame_bytes = 4; @@ -4499,6 +4505,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile { * instead of a hard-coded default; 0 (an older gateway that never set the field) means * "use the worker's built-in default". Sits above the public gRPC cap by an * envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + * Every worker->gateway frame — events, heartbeats, faults, and control replies + * including DrainEvents — must serialize within this limit; reply builders truncate + * to fit rather than emit an oversized frame. * * * uint32 max_frame_bytes = 4; @@ -4515,6 +4524,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile { * instead of a hard-coded default; 0 (an older gateway that never set the field) means * "use the worker's built-in default". Sits above the public gRPC cap by an * envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + * Every worker->gateway frame — events, heartbeats, faults, and control replies + * including DrainEvents — must serialize within this limit; reply builders truncate + * to fit rather than emit an oversized frame. * * * uint32 max_frame_bytes = 4; @@ -4535,6 +4547,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile { * instead of a hard-coded default; 0 (an older gateway that never set the field) means * "use the worker's built-in default". Sits above the public gRPC cap by an * envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + * Every worker->gateway frame — events, heartbeats, faults, and control replies + * including DrainEvents — must serialize within this limit; reply builders truncate + * to fit rather than emit an oversized frame. * * * uint32 max_frame_bytes = 4; diff --git a/clients/java/zb-mom-ww-mxgateway-client/build.gradle b/clients/java/zb-mom-ww-mxgateway-client/build.gradle index cdb2b4a..4fe4351 100644 --- a/clients/java/zb-mom-ww-mxgateway-client/build.gradle +++ b/clients/java/zb-mom-ww-mxgateway-client/build.gradle @@ -63,10 +63,11 @@ protobuf { // or a plugin/protobuf version bump, silently drifts the committed output. checkGeneratedClean // fails when the regenerated tree differs from what is committed. // -// Caveat (repo memory project_java_generated_churn): the protobuf gradle plugin also rewrites -// MxaccessGateway.java with a spurious protobuf-runtime-version delta on every build even when no -// .proto changed. CI reverts that one file (git checkout) before invoking this task; locally, do the -// same when you did not touch a .proto. See docs/GatewayTesting.md "Continuous Integration". +// The grpc/protobuf toolchain is pinned (build.gradle: grpcVersion / protobufVersion), so a +// regeneration is byte-identical to the committed single-file aggregates modulo real .proto +// changes — no spurious protobuf-runtime-version churn (IPC-24 verified this and deleted the old +// unconditional CI churn-revert step, which masked message-level drift). Regenerate and commit +// after any .proto change. See docs/GatewayTesting.md "Continuous Integration". tasks.register('checkGeneratedClean') { group = 'verification' description = 'Fails if the committed generated Java tree differs from a fresh regeneration.' @@ -83,9 +84,9 @@ tasks.register('checkGeneratedClean') { def dirty = stdout.toString().trim() if (!dirty.isEmpty()) { throw new GradleException( - "Generated Java is stale or churned:\n${dirty}\n" + - "Regenerate and commit after a .proto change, or 'git checkout' the spurious " + - "MxaccessGateway.java protobuf-version churn when no .proto changed.") + "Generated Java is stale:\n${dirty}\n" + + "Regenerate and commit the Java client after a .proto change " + + "(gradle :zb-mom-ww-mxgateway-client:generateProto).") } } } diff --git a/clients/proto/descriptors/mxaccessgw-client-v1.protoset b/clients/proto/descriptors/mxaccessgw-client-v1.protoset index b42b7921536a2bf6fc6e2aca15b3972122211cbb..c3d6da883fdb94471b2b8c35106e68dcf372c4c4 100644 GIT binary patch delta 16362 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 T1ezl>g~+b%nR$ix)fxW>)I6X? delta 15481 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 diff --git a/clients/python/src/zb_mom_ww_mxgateway/generated/mxaccess_worker_pb2.py b/clients/python/src/zb_mom_ww_mxgateway/generated/mxaccess_worker_pb2.py index 9344395..98db30a 100644 --- a/clients/python/src/zb_mom_ww_mxgateway/generated/mxaccess_worker_pb2.py +++ b/clients/python/src/zb_mom_ww_mxgateway/generated/mxaccess_worker_pb2.py @@ -27,7 +27,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__ import mxaccess_gateway_pb2 as mxaccess__gateway__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15mxaccess_worker.proto\x12\x12mxaccess_worker.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x16mxaccess_gateway.proto\"\x95\x06\n\x0eWorkerEnvelope\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12\x10\n\x08sequence\x18\x03 \x01(\x04\x12\x16\n\x0e\x63orrelation_id\x18\x04 \x01(\t\x12\x39\n\rgateway_hello\x18\n \x01(\x0b\x32 .mxaccess_worker.v1.GatewayHelloH\x00\x12\x37\n\x0cworker_hello\x18\x0b \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerHelloH\x00\x12\x37\n\x0cworker_ready\x18\x0c \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerReadyH\x00\x12;\n\x0eworker_command\x18\r \x01(\x0b\x32!.mxaccess_worker.v1.WorkerCommandH\x00\x12\x46\n\x14worker_command_reply\x18\x0e \x01(\x0b\x32&.mxaccess_worker.v1.WorkerCommandReplyH\x00\x12\x39\n\rworker_cancel\x18\x0f \x01(\x0b\x32 .mxaccess_worker.v1.WorkerCancelH\x00\x12=\n\x0fworker_shutdown\x18\x10 \x01(\x0b\x32\".mxaccess_worker.v1.WorkerShutdownH\x00\x12\x44\n\x13worker_shutdown_ack\x18\x11 \x01(\x0b\x32%.mxaccess_worker.v1.WorkerShutdownAckH\x00\x12\x37\n\x0cworker_event\x18\x12 \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerEventH\x00\x12?\n\x10worker_heartbeat\x18\x13 \x01(\x0b\x32#.mxaccess_worker.v1.WorkerHeartbeatH\x00\x12\x37\n\x0cworker_fault\x18\x14 \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerFaultH\x00\x42\x06\n\x04\x62ody\"Z\n\x0cGatewayHello\x12\"\n\x1asupported_protocol_version\x18\x01 \x01(\r\x12\r\n\x05nonce\x18\x02 \x01(\t\x12\x17\n\x0fgateway_version\x18\x03 \x01(\t\"i\n\x0bWorkerHello\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\r\n\x05nonce\x18\x02 \x01(\t\x12\x19\n\x11worker_process_id\x18\x03 \x01(\x05\x12\x16\n\x0eworker_version\x18\x04 \x01(\t\"\x8e\x01\n\x0bWorkerReady\x12\x19\n\x11worker_process_id\x18\x01 \x01(\x05\x12\x17\n\x0fmxaccess_progid\x18\x02 \x01(\t\x12\x16\n\x0emxaccess_clsid\x18\x03 \x01(\t\x12\x33\n\x0fready_timestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"w\n\rWorkerCommand\x12/\n\x07\x63ommand\x18\x01 \x01(\x0b\x32\x1e.mxaccess_gateway.v1.MxCommand\x12\x35\n\x11\x65nqueue_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x81\x01\n\x12WorkerCommandReply\x12\x32\n\x05reply\x18\x01 \x01(\x0b\x32#.mxaccess_gateway.v1.MxCommandReply\x12\x37\n\x13\x63ompleted_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x1e\n\x0cWorkerCancel\x12\x0e\n\x06reason\x18\x01 \x01(\t\"Q\n\x0eWorkerShutdown\x12/\n\x0cgrace_period\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x0e\n\x06reason\x18\x02 \x01(\t\"H\n\x11WorkerShutdownAck\x12\x33\n\x06status\x18\x01 \x01(\x0b\x32#.mxaccess_gateway.v1.ProtocolStatus\":\n\x0bWorkerEvent\x12+\n\x05\x65vent\x18\x01 \x01(\x0b\x32\x1c.mxaccess_gateway.v1.MxEvent\"\xa5\x02\n\x0fWorkerHeartbeat\x12\x19\n\x11worker_process_id\x18\x01 \x01(\x05\x12.\n\x05state\x18\x02 \x01(\x0e\x32\x1f.mxaccess_worker.v1.WorkerState\x12?\n\x1blast_sta_activity_timestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15pending_command_count\x18\x04 \x01(\r\x12\"\n\x1aoutbound_event_queue_depth\x18\x05 \x01(\r\x12\x1b\n\x13last_event_sequence\x18\x06 \x01(\x04\x12&\n\x1e\x63urrent_command_correlation_id\x18\x07 \x01(\t\"\xf4\x01\n\x0bWorkerFault\x12\x39\n\x08\x63\x61tegory\x18\x01 \x01(\x0e\x32\'.mxaccess_worker.v1.WorkerFaultCategory\x12\x16\n\x0e\x63ommand_method\x18\x02 \x01(\t\x12\x14\n\x07hresult\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\x0e\x65xception_type\x18\x04 \x01(\t\x12\x1a\n\x12\x64iagnostic_message\x18\x05 \x01(\t\x12<\n\x0fprotocol_status\x18\x06 \x01(\x0b\x32#.mxaccess_gateway.v1.ProtocolStatusB\n\n\x08_hresult*\x97\x02\n\x0bWorkerState\x12\x1c\n\x18WORKER_STATE_UNSPECIFIED\x10\x00\x12\x19\n\x15WORKER_STATE_STARTING\x10\x01\x12\x1c\n\x18WORKER_STATE_HANDSHAKING\x10\x02\x12!\n\x1dWORKER_STATE_INITIALIZING_STA\x10\x03\x12\x16\n\x12WORKER_STATE_READY\x10\x04\x12\"\n\x1eWORKER_STATE_EXECUTING_COMMAND\x10\x05\x12\x1e\n\x1aWORKER_STATE_SHUTTING_DOWN\x10\x06\x12\x18\n\x14WORKER_STATE_STOPPED\x10\x07\x12\x18\n\x14WORKER_STATE_FAULTED\x10\x08*\xc7\x04\n\x13WorkerFaultCategory\x12%\n!WORKER_FAULT_CATEGORY_UNSPECIFIED\x10\x00\x12+\n\'WORKER_FAULT_CATEGORY_INVALID_ARGUMENTS\x10\x01\x12\x37\n3WORKER_FAULT_CATEGORY_GATEWAY_AUTHENTICATION_FAILED\x10\x02\x12+\n\'WORKER_FAULT_CATEGORY_PROTOCOL_MISMATCH\x10\x03\x12,\n(WORKER_FAULT_CATEGORY_PROTOCOL_VIOLATION\x10\x04\x12+\n\'WORKER_FAULT_CATEGORY_PIPE_DISCONNECTED\x10\x05\x12\x32\n.WORKER_FAULT_CATEGORY_MXACCESS_CREATION_FAILED\x10\x06\x12\x31\n-WORKER_FAULT_CATEGORY_MXACCESS_COMMAND_FAILED\x10\x07\x12:\n6WORKER_FAULT_CATEGORY_MXACCESS_EVENT_CONVERSION_FAILED\x10\x08\x12\"\n\x1eWORKER_FAULT_CATEGORY_STA_HUNG\x10\t\x12(\n$WORKER_FAULT_CATEGORY_QUEUE_OVERFLOW\x10\n\x12*\n&WORKER_FAULT_CATEGORY_SHUTDOWN_TIMEOUT\x10\x0b\x42&\xaa\x02#ZB.MOM.WW.MxGateway.Contracts.Protob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15mxaccess_worker.proto\x12\x12mxaccess_worker.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x16mxaccess_gateway.proto\"\x95\x06\n\x0eWorkerEnvelope\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12\x10\n\x08sequence\x18\x03 \x01(\x04\x12\x16\n\x0e\x63orrelation_id\x18\x04 \x01(\t\x12\x39\n\rgateway_hello\x18\n \x01(\x0b\x32 .mxaccess_worker.v1.GatewayHelloH\x00\x12\x37\n\x0cworker_hello\x18\x0b \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerHelloH\x00\x12\x37\n\x0cworker_ready\x18\x0c \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerReadyH\x00\x12;\n\x0eworker_command\x18\r \x01(\x0b\x32!.mxaccess_worker.v1.WorkerCommandH\x00\x12\x46\n\x14worker_command_reply\x18\x0e \x01(\x0b\x32&.mxaccess_worker.v1.WorkerCommandReplyH\x00\x12\x39\n\rworker_cancel\x18\x0f \x01(\x0b\x32 .mxaccess_worker.v1.WorkerCancelH\x00\x12=\n\x0fworker_shutdown\x18\x10 \x01(\x0b\x32\".mxaccess_worker.v1.WorkerShutdownH\x00\x12\x44\n\x13worker_shutdown_ack\x18\x11 \x01(\x0b\x32%.mxaccess_worker.v1.WorkerShutdownAckH\x00\x12\x37\n\x0cworker_event\x18\x12 \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerEventH\x00\x12?\n\x10worker_heartbeat\x18\x13 \x01(\x0b\x32#.mxaccess_worker.v1.WorkerHeartbeatH\x00\x12\x37\n\x0cworker_fault\x18\x14 \x01(\x0b\x32\x1f.mxaccess_worker.v1.WorkerFaultH\x00\x42\x06\n\x04\x62ody\"s\n\x0cGatewayHello\x12\"\n\x1asupported_protocol_version\x18\x01 \x01(\r\x12\r\n\x05nonce\x18\x02 \x01(\t\x12\x17\n\x0fgateway_version\x18\x03 \x01(\t\x12\x17\n\x0fmax_frame_bytes\x18\x04 \x01(\r\"i\n\x0bWorkerHello\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\r\n\x05nonce\x18\x02 \x01(\t\x12\x19\n\x11worker_process_id\x18\x03 \x01(\x05\x12\x16\n\x0eworker_version\x18\x04 \x01(\t\"\x8e\x01\n\x0bWorkerReady\x12\x19\n\x11worker_process_id\x18\x01 \x01(\x05\x12\x17\n\x0fmxaccess_progid\x18\x02 \x01(\t\x12\x16\n\x0emxaccess_clsid\x18\x03 \x01(\t\x12\x33\n\x0fready_timestamp\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"w\n\rWorkerCommand\x12/\n\x07\x63ommand\x18\x01 \x01(\x0b\x32\x1e.mxaccess_gateway.v1.MxCommand\x12\x35\n\x11\x65nqueue_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x81\x01\n\x12WorkerCommandReply\x12\x32\n\x05reply\x18\x01 \x01(\x0b\x32#.mxaccess_gateway.v1.MxCommandReply\x12\x37\n\x13\x63ompleted_timestamp\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x1e\n\x0cWorkerCancel\x12\x0e\n\x06reason\x18\x01 \x01(\t\"Q\n\x0eWorkerShutdown\x12/\n\x0cgrace_period\x18\x01 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x0e\n\x06reason\x18\x02 \x01(\t\"H\n\x11WorkerShutdownAck\x12\x33\n\x06status\x18\x01 \x01(\x0b\x32#.mxaccess_gateway.v1.ProtocolStatus\":\n\x0bWorkerEvent\x12+\n\x05\x65vent\x18\x01 \x01(\x0b\x32\x1c.mxaccess_gateway.v1.MxEvent\"\xa5\x02\n\x0fWorkerHeartbeat\x12\x19\n\x11worker_process_id\x18\x01 \x01(\x05\x12.\n\x05state\x18\x02 \x01(\x0e\x32\x1f.mxaccess_worker.v1.WorkerState\x12?\n\x1blast_sta_activity_timestamp\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15pending_command_count\x18\x04 \x01(\r\x12\"\n\x1aoutbound_event_queue_depth\x18\x05 \x01(\r\x12\x1b\n\x13last_event_sequence\x18\x06 \x01(\x04\x12&\n\x1e\x63urrent_command_correlation_id\x18\x07 \x01(\t\"\xf4\x01\n\x0bWorkerFault\x12\x39\n\x08\x63\x61tegory\x18\x01 \x01(\x0e\x32\'.mxaccess_worker.v1.WorkerFaultCategory\x12\x16\n\x0e\x63ommand_method\x18\x02 \x01(\t\x12\x14\n\x07hresult\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\x0e\x65xception_type\x18\x04 \x01(\t\x12\x1a\n\x12\x64iagnostic_message\x18\x05 \x01(\t\x12<\n\x0fprotocol_status\x18\x06 \x01(\x0b\x32#.mxaccess_gateway.v1.ProtocolStatusB\n\n\x08_hresult*\x97\x02\n\x0bWorkerState\x12\x1c\n\x18WORKER_STATE_UNSPECIFIED\x10\x00\x12\x19\n\x15WORKER_STATE_STARTING\x10\x01\x12\x1c\n\x18WORKER_STATE_HANDSHAKING\x10\x02\x12!\n\x1dWORKER_STATE_INITIALIZING_STA\x10\x03\x12\x16\n\x12WORKER_STATE_READY\x10\x04\x12\"\n\x1eWORKER_STATE_EXECUTING_COMMAND\x10\x05\x12\x1e\n\x1aWORKER_STATE_SHUTTING_DOWN\x10\x06\x12\x18\n\x14WORKER_STATE_STOPPED\x10\x07\x12\x18\n\x14WORKER_STATE_FAULTED\x10\x08*\xc7\x04\n\x13WorkerFaultCategory\x12%\n!WORKER_FAULT_CATEGORY_UNSPECIFIED\x10\x00\x12+\n\'WORKER_FAULT_CATEGORY_INVALID_ARGUMENTS\x10\x01\x12\x37\n3WORKER_FAULT_CATEGORY_GATEWAY_AUTHENTICATION_FAILED\x10\x02\x12+\n\'WORKER_FAULT_CATEGORY_PROTOCOL_MISMATCH\x10\x03\x12,\n(WORKER_FAULT_CATEGORY_PROTOCOL_VIOLATION\x10\x04\x12+\n\'WORKER_FAULT_CATEGORY_PIPE_DISCONNECTED\x10\x05\x12\x32\n.WORKER_FAULT_CATEGORY_MXACCESS_CREATION_FAILED\x10\x06\x12\x31\n-WORKER_FAULT_CATEGORY_MXACCESS_COMMAND_FAILED\x10\x07\x12:\n6WORKER_FAULT_CATEGORY_MXACCESS_EVENT_CONVERSION_FAILED\x10\x08\x12\"\n\x1eWORKER_FAULT_CATEGORY_STA_HUNG\x10\t\x12(\n$WORKER_FAULT_CATEGORY_QUEUE_OVERFLOW\x10\n\x12*\n&WORKER_FAULT_CATEGORY_SHUTDOWN_TIMEOUT\x10\x0b\x42&\xaa\x02#ZB.MOM.WW.MxGateway.Contracts.Protob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -35,32 +35,32 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'mxaccess_worker_pb2', _glob if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'\252\002#ZB.MOM.WW.MxGateway.Contracts.Proto' - _globals['_WORKERSTATE']._serialized_start=2316 - _globals['_WORKERSTATE']._serialized_end=2595 - _globals['_WORKERFAULTCATEGORY']._serialized_start=2598 - _globals['_WORKERFAULTCATEGORY']._serialized_end=3181 + _globals['_WORKERSTATE']._serialized_start=2341 + _globals['_WORKERSTATE']._serialized_end=2620 + _globals['_WORKERFAULTCATEGORY']._serialized_start=2623 + _globals['_WORKERFAULTCATEGORY']._serialized_end=3206 _globals['_WORKERENVELOPE']._serialized_start=135 _globals['_WORKERENVELOPE']._serialized_end=924 _globals['_GATEWAYHELLO']._serialized_start=926 - _globals['_GATEWAYHELLO']._serialized_end=1016 - _globals['_WORKERHELLO']._serialized_start=1018 - _globals['_WORKERHELLO']._serialized_end=1123 - _globals['_WORKERREADY']._serialized_start=1126 - _globals['_WORKERREADY']._serialized_end=1268 - _globals['_WORKERCOMMAND']._serialized_start=1270 - _globals['_WORKERCOMMAND']._serialized_end=1389 - _globals['_WORKERCOMMANDREPLY']._serialized_start=1392 - _globals['_WORKERCOMMANDREPLY']._serialized_end=1521 - _globals['_WORKERCANCEL']._serialized_start=1523 - _globals['_WORKERCANCEL']._serialized_end=1553 - _globals['_WORKERSHUTDOWN']._serialized_start=1555 - _globals['_WORKERSHUTDOWN']._serialized_end=1636 - _globals['_WORKERSHUTDOWNACK']._serialized_start=1638 - _globals['_WORKERSHUTDOWNACK']._serialized_end=1710 - _globals['_WORKEREVENT']._serialized_start=1712 - _globals['_WORKEREVENT']._serialized_end=1770 - _globals['_WORKERHEARTBEAT']._serialized_start=1773 - _globals['_WORKERHEARTBEAT']._serialized_end=2066 - _globals['_WORKERFAULT']._serialized_start=2069 - _globals['_WORKERFAULT']._serialized_end=2313 + _globals['_GATEWAYHELLO']._serialized_end=1041 + _globals['_WORKERHELLO']._serialized_start=1043 + _globals['_WORKERHELLO']._serialized_end=1148 + _globals['_WORKERREADY']._serialized_start=1151 + _globals['_WORKERREADY']._serialized_end=1293 + _globals['_WORKERCOMMAND']._serialized_start=1295 + _globals['_WORKERCOMMAND']._serialized_end=1414 + _globals['_WORKERCOMMANDREPLY']._serialized_start=1417 + _globals['_WORKERCOMMANDREPLY']._serialized_end=1546 + _globals['_WORKERCANCEL']._serialized_start=1548 + _globals['_WORKERCANCEL']._serialized_end=1578 + _globals['_WORKERSHUTDOWN']._serialized_start=1580 + _globals['_WORKERSHUTDOWN']._serialized_end=1661 + _globals['_WORKERSHUTDOWNACK']._serialized_start=1663 + _globals['_WORKERSHUTDOWNACK']._serialized_end=1735 + _globals['_WORKEREVENT']._serialized_start=1737 + _globals['_WORKEREVENT']._serialized_end=1795 + _globals['_WORKERHEARTBEAT']._serialized_start=1798 + _globals['_WORKERHEARTBEAT']._serialized_end=2091 + _globals['_WORKERFAULT']._serialized_start=2094 + _globals['_WORKERFAULT']._serialized_end=2338 # @@protoc_insertion_point(module_scope) diff --git a/clients/rust/protos/mxaccess_gateway.proto b/clients/rust/protos/mxaccess_gateway.proto index d3e3a53..be96d5a 100644 --- a/clients/rust/protos/mxaccess_gateway.proto +++ b/clients/rust/protos/mxaccess_gateway.proto @@ -676,6 +676,10 @@ message WorkerInfoReply { } message DrainEventsReply { + // The reply is bounded by both a server-side count cap and the negotiated + // worker-frame byte cap; a reply may therefore carry fewer events than + // `max_events` and fewer than are queued. Callers drain iteratively until an + // empty reply. repeated MxEvent events = 1; } @@ -760,6 +764,11 @@ message ReplayGap { // after_worker_sequence = oldest_available_sequence - 1 in the next // StreamEventsRequest, which will cause the server to replay starting at // oldest_available_sequence (the first retained event). + // When nothing is retained (the replay ring is empty), this is the next sequence + // that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + // formula remains valid: it resolves to the highest sequence already seen, so the + // follow-up resume replays nothing, reports no gap, and every newer live event + // passes. The interval evicted is unchanged. uint64 oldest_available_sequence = 2; } diff --git a/clients/rust/protos/mxaccess_worker.proto b/clients/rust/protos/mxaccess_worker.proto index e50c4ff..7a8a5e7 100644 --- a/clients/rust/protos/mxaccess_worker.proto +++ b/clients/rust/protos/mxaccess_worker.proto @@ -47,6 +47,9 @@ message GatewayHello { // instead of a hard-coded default; 0 (an older gateway that never set the field) means // "use the worker's built-in default". Sits above the public gRPC cap by an // envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + // Every worker->gateway frame — events, heartbeats, faults, and control replies + // including DrainEvents — must serialize within this limit; reply builders truncate + // to fit rather than emit an oversized frame. uint32 max_frame_bytes = 4; } diff --git a/docs/ClientProtoGeneration.md b/docs/ClientProtoGeneration.md index fa21cb7..270ccf5 100644 --- a/docs/ClientProtoGeneration.md +++ b/docs/ClientProtoGeneration.md @@ -88,15 +88,23 @@ scripts assert the pin and resolve tools from `PATH`: | Generator | Pinned version | Guard | |-----------|----------------|-------| | protoc (descriptor set) | 34.1 | version assertion in `scripts/publish-client-proto-inputs.ps1` | -| `Grpc.Tools` (C# `Generated/`) | 2.80.0 (contracts csproj) | `scripts/check-codegen.ps1` git-diff of `Generated/` | -| `grpcio-tools` (Python) | 1.80.0 (protobuf runtime 6.31.1) | version assertion in `clients/python/generate-proto.ps1` | -| protobuf / grpc-java (Java) | `protobufVersion` / `grpcVersion` in `clients/java/build.gradle` | `checkGeneratedClean` gradle task | +| `Grpc.Tools` (C# `Generated/`) | 2.80.0 (contracts csproj) | `scripts/check-codegen.ps1` git-diff of `Generated/` (Check 2) | +| `protoc-gen-go` (Go) | v1.36.11 | version assertion in `clients/go/generate-proto.ps1`; `check-codegen.ps1` Check 4 | +| `protoc-gen-go-grpc` (Go) | 1.6.2 | version assertion in `clients/go/generate-proto.ps1`; `check-codegen.ps1` Check 4 | +| `grpcio-tools` (Python) | 1.80.0 (protobuf runtime 6.31.1) | version assertion in `clients/python/generate-proto.ps1`; `check-codegen.ps1` Check 4 | +| protobuf / grpc-java (Java) | `protobufVersion` / `grpcVersion` in `clients/java/build.gradle` | CI `git diff --exit-code` over `clients/java/src/main/generated` after `gradle test` (the `checkGeneratedClean` gradle task is the equivalent local check) | A newer `grpcio-tools` stamps a `GRPC_GENERATED_VERSION` above the pinned grpcio -runtime and breaks Python `pytest`; the Java protobuf plugin rewrites -`MxaccessGateway.java` with spurious protobuf-runtime-version churn on every build -(revert that one file when no `.proto` changed — see -[Gateway Testing](./GatewayTesting.md) "Continuous Integration"). +runtime and breaks Python `pytest`, so the Python and Go scripts assert their +generator pins before regenerating. Under the pinned grpc/protobuf toolchain the +Java protobuf plugin regenerates byte-identical output (modulo real `.proto` +changes), so CI enforces Java freshness with a direct `git diff --exit-code -- clients/java/src/main/generated` +step after `gradle test` (which transitively regenerates via `generateProto`); the +`checkGeneratedClean` gradle task is the equivalent check for local/manual use. The +old unconditional churn-revert CI step (which masked message-level drift in the +single-file Java aggregates) was deleted (IPC-24). Go and Python committed +bindings are guarded by `check-codegen.ps1` **Check 4**, which regenerates both +and fails on any diff. ## Output Directories diff --git a/docs/Contracts.md b/docs/Contracts.md index f6ce3db..fc53b25 100644 --- a/docs/Contracts.md +++ b/docs/Contracts.md @@ -114,7 +114,11 @@ dotnet build src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csp `scripts/check-codegen.ps1` enforces this in CI (it force-regenerates and fails on any `git diff` against the committed `Generated/`) — that regeneration diff in the `portable` job is the primary guard. The SSH-driven `windows-x86` job's net48 worker build is the -secondary guard (a stale `Generated/` also breaks the x86 build with `CS0246`). +secondary guard (a stale `Generated/` also breaks the x86 build with `CS0246`). The same +script runs four checks in total: the committed client descriptor set (Check 1), the C# +`Generated/` (Check 2), the Rust vendored protos (Check 3), and the Go/Python client bindings +(Check 4) each regenerate and fail on any diff. See +[Client Proto Generation](./ClientProtoGeneration.md) for the pinned generator versions. Client generation inputs are published through `clients/proto/proto-inputs.json` and the descriptor set under diff --git a/docs/GatewayTesting.md b/docs/GatewayTesting.md index 074a20f..f55b9aa 100644 --- a/docs/GatewayTesting.md +++ b/docs/GatewayTesting.md @@ -428,10 +428,13 @@ runtime because the x86 Worker cannot build on Linux: gateway fake-worker tests, and builds/tests the clients that run on Linux: .NET client build, Go (`gofmt` + `go build` + `go test`), Rust (`cargo fmt --check` + `cargo test` + `cargo clippy -D warnings`), and Python (`pytest`). -- **`java`** (Linux, JDK 17) — `gradle test`. The protobuf gradle plugin rewrites - `MxaccessGateway.java` with spurious protobuf-runtime-version churn on every build, so - when no `.proto` changed the job reverts that one file (`git checkout`) before asserting - the generated tree is clean. The dev Mac has no JRE, so Java verification is CI-only. +- **`java`** (Linux, JDK 17) — `gradle test`, then `git diff --exit-code` over the generated + tree. The grpc/protobuf toolchain is pinned (`clients/java/build.gradle`), so a regeneration + is byte-identical to the committed single-file aggregates modulo real `.proto` changes; the + git-diff is therefore a true drift gate that now catches message-level proto drift in the Java + client (IPC-24 deleted the old unconditional churn-revert step, which masked exactly that + class). The dev Mac has a Homebrew JDK 17, so `gradle generateProto` can be run there to refresh + the Java aggregates when a `.proto` changes. - **`windows-x86`** (Linux runner, per push/PR) — builds the **x86 / net48 Worker and Worker.Tests**, which are Windows-only and out of scope for the Linux jobs. It runs on a Linux runner that always schedules and SSHes to windev (`10.100.0.48`), where @@ -449,10 +452,14 @@ runtime because the x86 Worker cannot build on Linux: `xUnit1030`). Gated `if: github.event_name == 'schedule'`, so it never gates a push. On failure it opens a Gitea issue via the Actions token, since nobody watches the Actions page. -The freshness guard `scripts/check-codegen.ps1` fails the build when the committed -client descriptor set or the C# `Generated/` no longer matches the current `.proto` -sources — the codegen drift class this repo has hit repeatedly (stale client -descriptors, net48 `CS0246` on unregenerated protos). The **primary** guard for the +The freshness guard `scripts/check-codegen.ps1` runs four checks and fails the build when the +committed client descriptor set (Check 1), the C# `Generated/` (Check 2), the Rust vendored +protos (Check 3), or the Go/Python client bindings (Check 4, IPC-25) no longer match the current +`.proto` sources — the codegen drift class this repo has hit repeatedly (stale client +descriptors, net48 `CS0246` on unregenerated protos, silently stale Go/Python worker bindings). +Check 4 regenerates the Go and Python bindings with their pinned generators (`protoc-gen-go` +v1.36.11 / `protoc-gen-go-grpc` 1.6.2, `grpcio-tools` 1.80.0) and fails on any diff; a missing +generator fails the check rather than skipping it. The **primary** guard for the "regenerate and commit `Generated/`" rule is that regeneration diff in the `portable` job; the `windows-x86` net48 compile is the **secondary** guard (a stale `Generated/` also breaks the x86 build with `CS0246`). See [Client Proto Generation](./ClientProtoGeneration.md) and diff --git a/scripts/check-codegen.ps1 b/scripts/check-codegen.ps1 index d7e4177..ab19901 100644 --- a/scripts/check-codegen.ps1 +++ b/scripts/check-codegen.ps1 @@ -1,7 +1,7 @@ #!/usr/bin/env pwsh -# Codegen freshness guard for CI (IPC-01, IPC-19, IPC-20, CLI-02). +# Codegen freshness guard for CI (IPC-01, IPC-19, IPC-20, IPC-25, CLI-02). # -# Three checks, all Linux/macOS-runnable (no Server build, no x86 worker): +# Four checks, all Linux/macOS-runnable (no Server build, no x86 worker): # 1. Published client descriptor set matches the current .proto sources (delegates to # publish-client-proto-inputs.ps1 -Check, which normalizes source_code_info so it is # protoc-version tolerant). @@ -13,6 +13,12 @@ # crate buildable outside the repo, CLI-02) are byte-identical to the canonical Contracts # protos. A drift means a .proto was edited without refreshing the vendored copies, which would # publish a stale wire contract to crate consumers while the in-repo build stays correct. +# 4. The committed Go and Python client bindings match a fresh regeneration (IPC-25). The two +# per-client generate-proto.ps1 scripts pin their generators (protoc-gen-go v1.36.11 / +# protoc-gen-go-grpc v1.6.2 for Go; grpcio-tools 1.80.0 for Python), so a clean checkout +# regenerates deterministic output; a non-empty git diff means a .proto was edited without +# regenerating and committing those bindings. A missing generator FAILS the check (a skipped +# guard is the exact silent-drift hole IPC-25 closes), never skips it. # # The x86 Worker + Worker.Tests are Windows-only and are guarded by the SSH-driven `windows-x86` # CI job (see docs/GatewayTesting.md, Continuous Integration), not here. @@ -28,7 +34,7 @@ $generatedDir = Join-Path $repoRoot 'src/ZB.MOM.WW.MxGateway.Contracts/Generated $contractsProject = Join-Path $repoRoot 'src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj' $failures = New-Object System.Collections.Generic.List[string] -Write-Host '== Check 1/2: client descriptor set freshness ==' +Write-Host '== Check 1/4: client descriptor set freshness ==' try { & (Join-Path $PSScriptRoot 'publish-client-proto-inputs.ps1') -Check if ($LASTEXITCODE -ne 0) { @@ -40,7 +46,7 @@ catch { } Write-Host '' -Write-Host '== Check 2/2: Contracts/Generated matches a fresh regeneration ==' +Write-Host '== Check 2/4: Contracts/Generated matches a fresh regeneration ==' try { # Force a full regeneration: Grpc.Tools skips regen when the committed .cs look up to date, so # remove them first (the documented "del Generated/*.cs to force regen" trick). @@ -66,7 +72,7 @@ catch { } Write-Host '' -Write-Host '== Check 3/3: Rust vendored protos match canonical Contracts protos ==' +Write-Host '== Check 3/4: Rust vendored protos match canonical Contracts protos ==' try { $canonicalProtoDir = Join-Path $repoRoot 'src/ZB.MOM.WW.MxGateway.Contracts/Protos' $vendoredProtoDir = Join-Path $repoRoot 'clients/rust/protos' @@ -87,6 +93,28 @@ catch { $failures.Add("Rust vendored proto check failed: $($_.Exception.Message)") } +Write-Host '' +Write-Host '== Check 4/4: Go and Python client bindings match a fresh regeneration ==' +try { + # Regenerate both binding sets with their pinned generators, then diff. The per-client scripts + # throw on a missing or off-pin generator, so any failure here FAILS the check rather than + # skipping it (a skipped guard is exactly the silent-drift hole IPC-25 closes). + $goBindingDir = 'clients/go/internal/generated' + $pyBindingDir = 'clients/python/src/zb_mom_ww_mxgateway/generated' + + & (Join-Path $repoRoot 'clients/go/generate-proto.ps1') | Out-Host + & (Join-Path $repoRoot 'clients/python/generate-proto.ps1') | Out-Host + + $bindingDiff = (& git -C $repoRoot status --porcelain -- $goBindingDir $pyBindingDir | Out-String).Trim() + if (-not [string]::IsNullOrEmpty($bindingDiff)) { + Write-Host $bindingDiff + $failures.Add("Go/Python client bindings differ from a fresh regeneration. Run clients/go/generate-proto.ps1 and clients/python/generate-proto.ps1 with the pinned generators and commit $goBindingDir and $pyBindingDir.") + } +} +catch { + $failures.Add("Go/Python codegen check failed (tool missing or regeneration error): $($_.Exception.Message)") +} + Write-Host '' if ($failures.Count -gt 0) { Write-Host 'Codegen freshness check FAILED:' -ForegroundColor Red diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs b/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs index fe182a4..f78dc5b 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs +++ b/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessGateway.cs @@ -22796,6 +22796,12 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto { private static readonly pb::FieldCodec _repeated_events_codec = pb::FieldCodec.ForMessage(10, global::ZB.MOM.WW.MxGateway.Contracts.Proto.MxEvent.Parser); private readonly pbc::RepeatedField events_ = new pbc::RepeatedField(); + /// + /// The reply is bounded by both a server-side count cap and the negotiated + /// worker-frame byte cap; a reply may therefore carry fewer events than + /// `max_events` and fewer than are queued. Callers drain iteratively until an + /// empty reply. + /// [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] public pbc::RepeatedField Events { @@ -24510,6 +24516,11 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto { /// after_worker_sequence = oldest_available_sequence - 1 in the next /// StreamEventsRequest, which will cause the server to replay starting at /// oldest_available_sequence (the first retained event). + /// When nothing is retained (the replay ring is empty), this is the next sequence + /// that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + /// formula remains valid: it resolves to the highest sequence already seen, so the + /// follow-up resume replays nothing, reports no gap, and every newer live event + /// passes. The interval evicted is unchanged. /// [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessWorker.cs b/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessWorker.cs index d8e1444..892348a 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessWorker.cs +++ b/src/ZB.MOM.WW.MxGateway.Contracts/Generated/MxaccessWorker.cs @@ -1164,6 +1164,9 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto { /// instead of a hard-coded default; 0 (an older gateway that never set the field) means /// "use the worker's built-in default". Sits above the public gRPC cap by an /// envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + /// Every worker->gateway frame — events, heartbeats, faults, and control replies + /// including DrainEvents — must serialize within this limit; reply builders truncate + /// to fit rather than emit an oversized frame. /// [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto b/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto index d3e3a53..be96d5a 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto +++ b/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_gateway.proto @@ -676,6 +676,10 @@ message WorkerInfoReply { } message DrainEventsReply { + // The reply is bounded by both a server-side count cap and the negotiated + // worker-frame byte cap; a reply may therefore carry fewer events than + // `max_events` and fewer than are queued. Callers drain iteratively until an + // empty reply. repeated MxEvent events = 1; } @@ -760,6 +764,11 @@ message ReplayGap { // after_worker_sequence = oldest_available_sequence - 1 in the next // StreamEventsRequest, which will cause the server to replay starting at // oldest_available_sequence (the first retained event). + // When nothing is retained (the replay ring is empty), this is the next sequence + // that can be delivered — `highest observed + 1` — and the `oldest - 1` resume + // formula remains valid: it resolves to the highest sequence already seen, so the + // follow-up resume replays nothing, reports no gap, and every newer live event + // passes. The interval evicted is unchanged. uint64 oldest_available_sequence = 2; } diff --git a/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto b/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto index e50c4ff..7a8a5e7 100644 --- a/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto +++ b/src/ZB.MOM.WW.MxGateway.Contracts/Protos/mxaccess_worker.proto @@ -47,6 +47,9 @@ message GatewayHello { // instead of a hard-coded default; 0 (an older gateway that never set the field) means // "use the worker's built-in default". Sits above the public gRPC cap by an // envelope-overhead margin so an accepted gRPC payload always fits one worker frame. + // Every worker->gateway frame — events, heartbeats, faults, and control replies + // including DrainEvents — must serialize within this limit; reply builders truncate + // to fit rather than emit an oversized frame. uint32 max_frame_bytes = 4; }