diff --git a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs
index bff47b1..ce83155 100644
--- a/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs
+++ b/src/ZB.MOM.WW.MxGateway.Server/Workers/WorkerClient.cs
@@ -237,15 +237,22 @@ public sealed class WorkerClient : IWorkerClient
// CalculateSize. WriteLoopAsync stamps envelope.Sequence immediately before the write
// (GWC-28), and a non-zero varint field grows the encoding — so the number computed here
// is a lower bound on the frame the writer actually emits, never the frame length. Passing
- // it as a knownSize would under-length the prefix and desync the worker's framing. The
- // pre-check stays a pre-check: it is conservative in the right direction.
+ // it as a knownSize would under-length the prefix and desync the worker's framing.
+ //
+ // IPC-39: being a lower bound is conservative for the knownSize question but the wrong
+ // direction for this pre-check, which must not admit an envelope the writer will then
+ // reject with a session-fatal MessageTooLarge. Compare against the frame maximum less the
+ // MaxSequenceEncodingBytes the stamp can add, so every envelope this check accepts still
+ // fits once the sequence is written.
int envelopeSize = commandEnvelope.CalculateSize();
- if (envelopeSize > _connection.FrameOptions.MaxMessageBytes)
+ int preCheckLimit = _connection.FrameOptions.MaxMessageBytes - MaxSequenceEncodingBytes;
+ if (envelopeSize > preCheckLimit)
{
throw new WorkerClientException(
WorkerClientErrorCode.CommandTooLarge,
$"Worker command {method} serializes to {envelopeSize} bytes, exceeding the negotiated "
- + $"worker-frame maximum of {_connection.FrameOptions.MaxMessageBytes} bytes.");
+ + $"worker-frame maximum of {_connection.FrameOptions.MaxMessageBytes} bytes less the "
+ + $"{MaxSequenceEncodingBytes}-byte sequence-stamp allowance.");
}
await EnqueueAsync(commandEnvelope, cancellationToken).ConfigureAwait(false);
@@ -455,6 +462,18 @@ public sealed class WorkerClient : IWorkerClient
_stopCts.Dispose();
}
+ ///
+ /// Worst-case number of bytes 's sequence stamp adds to an
+ /// envelope that was sized before it: one tag byte plus a ten-byte varint, the maximum
+ /// encoding of a 64-bit protobuf field. The CommandTooLarge pre-check subtracts this
+ /// from the negotiated frame maximum (IPC-39) because it measures the envelope while
+ /// Sequence is still 0 — an unset field costs nothing to encode, so the measured size
+ /// is a lower bound on the frame the writer emits, and without the allowance an envelope in
+ /// the last eleven bytes of the range would pass the pre-check and then fault the session on
+ /// the write.
+ ///
+ private const int MaxSequenceEncodingBytes = 11;
+
/// Manages writing envelopes to the worker pipe.
private async Task WriteLoopAsync()
{