feat(sidecar): protocol 6 — carry the key, answer bridge.busy (Phase 11a) #36
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/protocol-v6-idempotency"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
EVENTS_PLAN.mdPhase 11a, the sidecar half.PROTOCOL_VERSION5 → 6, and one behaviour:bridge.busymaps to 425 Too Early in all three responders.Everything else is free, and that is the interesting part. The
idempotencyKeyrides in the command body, which every write endpoint already passes through verbatim;champ.boss.killedlands ineventsand on the feed through the generic forward path with no arm of its own. No store migration — nothing gains a column.That is the dumb-forwarder property doing its job (v3 §3), and it is worth naming what it means here specifically:
Why 425 and not 409
409 is already the protocol-version gate's answer, and the two want opposite dispositions from a client: a version mismatch is a deployment fault nobody should retry, and a busy shard is a retry that will succeed on its own. Sharing a status would make the difference readable only by inspecting the body — which is exactly how a retry loop ends up quietly swallowing a mismatched deployment.
425 is what that status is for: a server unwilling to risk processing a request that might be a replay. It is unambiguous here because nothing else on this surface uses it.
A replayed reply, by contrast, is an ordinary 200. The caller must be able to treat it exactly as it would have treated the answer it lost;
replayed: trueis for the log.The passthrough is documented rather than left implicit
admin_call's doc comment already said the body's "remaining fields pass straight through", andidempotencyKeyis one of those — so this half needed no code. It is now named explicitly, because a later refactor that narrowed the body to a known field list would silently turn every retried world write back into a possible duplicate, and nothing here would fail.The same comment records the fact that makes the shard's replay logic necessary:
reqIdis regenerated on every call, so a retry carries the same idempotency key under a new correlation id, and the shard has to re-stamp a replayed reply rather than echo the id the first attempt used.Verification
cargo test— 43 pass, 0 fail (4 new): 425 on all three responders, 425 ≠ the version gate's 409, a replayed reply is still a 200, and the pre-existing error mapping the busy arm is threaded in front of is unchanged.cargo fmt --checkandcargo clippy --all-targets -- -D warningsclean.--versionreportsprotocol 6,GET /healthreports"protocol":6, and both the key and the new kind arrived through the generic paths with no code here touching either.POST /admin/broadcast(two sharing a key) produced two rows in the shard's audit trail, with the repeat answered{"kind":"admin.ok","replayed":true,"reqId":"r-2","t":<the first attempt's t>}. Theid-correlated plane walked too, viaPOST /towncrier.bridge.busywas not reachable on the live rig, and that is a property of the shard rather than of this change: with today's synchronous handlers a repeat cannot arrive while the original is still running. The mapping is unit-tested; Phase 11b's leases are the first handlers that defer and therefore the first that can produce it.Plugin: RunicGateway/servuo-plugins#21 · Module: RunicGateway/Module-uo#29 · Docs: RunicGateway/docs#219
🤖 Generated with Claude Code