The #1 deploy hazard for the Arrow Flight feature: the py-mando wheel and the mando server are deployed independently, but they share wire types over Flight + REST. A develop merge that bumped a shared struct broke old-wheel ↔ new-server. The durable fix is to make every boundary wire type forward/backward tolerant with serde.
This is the single most likely thing to break a deploy
The wheel (mandarrow-client Flight wire types + the bess client Python API, e.g.
BessOptClient.send(update_id=...)) ships on its own cadence from the mando server. Any change to a struct that crosses the wire — Flight ticket/metadata OR REST request/response — can break a consumer running an older wheel against a newer server (or vice versa).
The incident
A develop merge bumped a shared DataPointUpdateInfo:
- renamed
fetch_time→update_time - added
update_id
This broke old-wheel ↔ new-server: the old wheel could not deserialize the new shape, and the send side surfaced as:
send() got an unexpected keyword argument 'update_id'
Root cause
The wire types were not tolerant of additive/renamed fields. An older deserializer encountering a renamed field (or a stricter struct rejecting unknown fields) fails the whole payload.
Fix (commit 73cbdcf3) — wire-type tolerance rules
Make every Flight + REST boundary struct tolerant:
| Rule | serde attribute | Why |
|---|---|---|
| Every optional / added field defaults | #[serde(default)] | A newer field absent in an old payload deserializes fine |
| Renames keep the old name readable | #[serde(alias = "old_name")] | Old payloads with the old field name still deserialize |
| Never reject unknown fields on boundary structs | NO #[serde(deny_unknown_fields)] | A newer field present in a new payload doesn’t blow up an old deserializer |
Shipped with compat tests: old-shape JSON must still deserialize into the current struct. These tests are the regression guard — they pin the contract that an old payload remains readable.
Going forward
For Agents — wire-type change checklist
When touching ANY struct that crosses Flight or REST:
- New fields →
#[serde(default)].- Renames → keep
#[serde(alias = "old")]on the new name.- Never add
#[serde(deny_unknown_fields)]to a boundary struct.- Add/extend a compat test that deserializes the old JSON shape. With these, additive changes are non-breaking in both directions.
The two hard constraints that remain
- You cannot retroactively fix already-released versions. Tolerance only helps versions built after the fix. If an old wheel is already in the wild without the alias/default, it stays broken against the new shape. So: when a wire type changes, deploy mando + the consumer wheel from the same build.
- The send-side mismatch is not serde-fixable.
send() got an unexpected keyword argument 'update_id'is a Python API signature mismatch in the bess client, not a deserialization issue. It only resolves by rebuilding the consumers on the matching wheel — there is no compatibility shim for a method signature.
Related
- be-1595-flight-rest-graceful-degradation-2026-06-24 — the other Flight deploy hazard
- be-1595-arrow-flight-dev-deploy-runbook-2026-06-24 — full dev deploy runbook (and the “rebuild consumers on the matching wheel” mechanics)
- be-1595-enabling-arrow-flight-consumers-2026-06-16 — runtime switch
- py-mando
- Arrow Flight Streaming Design