Compatibility is a matrix of versions, states, and failure paths
Build protocol conformance evidence from incremental parsing, rolling-version pairs, unknown fields, failure injection, and semantic invariants rather than happy-path round trips alone.
On this page
The question for today
What you’ll understand
- Separate wire compatibility from behavioral compatibility and safe deployment.
- Construct directional version-pair and state-transition tests.
- Explain why sampled test coverage cannot prove universal protocol correctness.
Two implementations exchanging one successful request proves one path works under one configuration. A rolling deployment asks much more: old and new peers, intermediaries, caches, partial messages, failures, and retries must continue honoring the intended contract. Define compatibility as specific preserved behavior over an explicit matrix. Then choose tests whose evidence supports that claim rather than assuming that a parseable message or a green integration test certifies the entire protocol.
Wire format and meaning can evolve differently
Wire compatibility asks whether an older or newer peer can decode a message under its format rules. Behavioral compatibility asks whether the decoded message still means what the application expects. Adding an optional field can be wire-compatible while changing the default behavior in a way an old client cannot request or interpret. Removing a field can leave old senders transmitting it. Preserve field identities and follow the format's evolution rules; for Protocol Buffers, reserve deleted field numbers to prevent later reuse, and reserve deleted field names when preserving JSON/TextFormat compatibility. Unknown fields and unknown enum values also need runtime-appropriate behavior rather than a blanket assumption of harmlessness.
Serialization paths matter. A binary round trip that preserves unknown fields can differ from a JSON conversion or an application rebuilding a message field by field. Schema acceptance alone therefore cannot prove a proxy preserves future information. Define required semantics for missing fields, zero values, unknown enum values, content types, status handling, and retry behavior. A stricter server validation rule can break old clients even if the wire grammar stays unchanged. Conversely, permissive parsing can preserve apparent availability while admitting ambiguous or unsupported state that downstream participants interpret differently.
Exercise state transitions and intermediary transformations
For each relevant direction, test old client to new server, new client to old server, and same-version baselines. Include negotiated settings and intermediaries that translate framing, cache responses, or terminate connections. Feed a valid byte sequence through every meaningful fragmentation point and verify that the emitted messages are unchanged. Coalesce multiple messages into one input too. Invalid lengths, truncated bodies, conflicting metadata, excessive resource requests, and forbidden frame states should produce the documented bounded failure rather than a hang, resource leak, or divergent interpretation between hops.
Failure injection should target boundaries that affect meaning: before transmission, after server receipt, after effect commit but before response, during a streaming half-close, and during retry reconciliation. Verify the business invariant as well as the status returned to the caller. A connection error can be expected while a duplicate side effect is unacceptable. Use deterministic fixtures and state traces so a failure can be reproduced, then broaden with fuzzing or generated sequences. Fuzzing explores inputs; the oracle still needs to specify what must remain true, such as bounded allocation, consistent framing, or at-most-one committed effect for a retained operation identity.
Make the claim as narrow as the evidence
A useful release artifact records protocol and schema versions, runtime versions, negotiated parameters, scenarios, expected invariants, actual outcomes, and known exclusions. Include resource and deadline bounds as observable behavior, not merely byte-for-byte output comparisons. If a matrix cell cannot be supported during a rolling upgrade, enforce an explicit rollout boundary or feature gate rather than relying on every peer updating simultaneously. Regression tests should preserve minimized failure traces so the same mechanism stays covered when code is refactored. Passing tests support the documented claim; they do not remove the need to reason about untested states and assumptions.
Use the probability lab to question coverage claims. Reset, explicitly set BOTH p=0.1 and n=10, predict the zero-success bar and its complement, then Play or Step. Reset and re-set BOTH p=0.1 and n=30 for comparison. Under independent selections, the chance of selecting the scenario at least once rises from about 65.1% to 95.8%, yet neither value certifies correctness. The exact bars do not run tests or model correlated generators. A rare scenario that is never generated remains invisible; combine targeted state coverage and sound invariants with random exploration instead of treating more trials as a universal guarantee.
Work it through
Budget a directional compatibility matrix
A service must support three client versions and three server versions during a rollout. The team defines eight deterministic scenarios for every ordered client/server pair. Each case runs three independent process restarts, without claiming those repeats cover new protocol states.
There are 3×3=9 ordered version pairs. Old-to-new and new-to-old are distinct cells because sending and receiving behavior can differ.
Nine pairs times eight scenarios gives 72 defined cases. Three process runs per case produce 216 executions, each requiring the same stated invariant checks.
The 216 executions do not imply 216 distinct state scenarios. Add intermediary modes, cache variants, and failure boundaries as explicit dimensions when they are part of the deployment claim rather than counting repeats as new coverage.
The matrix contains 72 defined cases and 216 executions. Its credibility comes from relevant scenarios and meaningful oracles, not the execution count alone.
The living notebook
Explore the model
Interactive lab · probability
Sampling a scenario is not proving a protocol
Reset, set BOTH p=0.1 and n=10, predict zero selections and its complement, then Play or Step. Reset and re-set BOTH p=0.1 and n=30. Exact independent binomial counts model hypothetical selections only; playback highlights outcomes rather than generating tests, and selection does not guarantee bug detection or correctness.
- Mean · np
- 5
- Variance · np(1−p)
- 2.5
- Total probability
- 1
These are exact binomial probabilities for independent trials with one fixed success probability. Playback tours the distribution; it does not resample outcomes. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forMore sampled cases can improve a stated selection probability while leaving important unmodeled states and correctness claims unproven.
Pause & reason
Check your understanding
Your turn
Try it yourself
Two client versions and four server versions require five state scenarios each. Calculate the ordered-pair case count. If a test generator never emits the post-commit lost-response state, does increasing random runs prove deduplication there?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Count the matrix explicitly, then distinguish probability within a generator's support from a scenario absent from that support.
Compare with the solution
There are 2×4=8 ordered pairs and 8×5=40 defined cases. More random runs cannot cover a state the generator never emits, so they do not prove deduplication after a committed effect loses its response. Add that targeted transition and verify durable operation state and effect count.
Keep the idea
Take it with you
- Wire acceptance and preserved application behavior are separate compatibility claims.
- Version direction, parser fragmentation, and failure states need explicit coverage.
- Execution counts and random sampling support only the invariants and scenarios actually tested.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.