feat(sidecar): protocol 6 — carry the idempotency key, answer bridge.busy (Phase 11a)
Some checks failed
PR Checks / rust-gates (pull_request) Failing after -16s
Some checks failed
PR Checks / rust-gates (pull_request) Failing after -16s
PROTOCOL_VERSION 5 -> 6, and one behaviour: `bridge.busy` maps to 425 Too Early in all three responders. Everything else is free, and that is the point. The key rides in the command body, which every write endpoint already passes through verbatim; `champ.boss.killed` lands in `events` and on the feed through the generic forward path with no arm of its own. No store migration — nothing gains a column. Worth naming what the dumb-forwarder property means here specifically: the sidecar makes no idempotency promise of its own. It does not dedupe, does not cache, and does not know what a key means. The guarantee is the shard's, end to end, which is the only place it can be. 425 rather than 409 because 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, a busy shard is a retry that will succeed. Sharing a status would make the difference readable only by inspecting the body, which is how a retry loop ends up hiding a mismatched deployment. A replayed reply is an ordinary 200 — `replayed: true` is for the log. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -72,7 +72,26 @@ use tracing_subscriber::EnvFilter;
|
||||
/// index only the columns they already had, so the new fields ride inside the stored JSON and the
|
||||
/// new kind lands in `events` like any other. That is the dumb-forwarder property doing its job:
|
||||
/// the sidecar defines no schema for a frame's contents and so needs no change when they grow.
|
||||
pub const PROTOCOL_VERSION: u32 = 5;
|
||||
///
|
||||
/// v6 (Protocol 6): the first bump that is about a GUARANTEE rather than about data, and the first
|
||||
/// the sidecar mostly gets for free. Two things:
|
||||
///
|
||||
/// * **`idempotencyKey` on inbound commands.** A command that carries one is executed by the shard
|
||||
/// at most once; a repeat is answered with the original reply rather than re-run. That is what
|
||||
/// makes a world-writing verb retryable at all — until now a lost acknowledgement was
|
||||
/// indistinguishable from a command that never applied, so the website had to declare every
|
||||
/// write un-retryable and accept losing one rather than risk doubling it. The sidecar's part is
|
||||
/// to CARRY the key (it rides in the command body, which every write endpoint already passes
|
||||
/// through verbatim) and to understand the one new answer the shard can now give: `bridge.busy`,
|
||||
/// meaning a command under that key is still in flight. See `web::respond`.
|
||||
/// * **`champ.boss.killed` is a new kind**: a champion's defeat, with the damage table only the
|
||||
/// shard ever sees. It was previously inferable from `champ.update` going `bossUp` true then
|
||||
/// false alongside a nearby `mob.killed`, which is fragile and says nothing about who did the
|
||||
/// work. It lands in `events` and on the feed like any other kind, with no code here at all —
|
||||
/// the dumb-forwarder property again.
|
||||
///
|
||||
/// **No store migration.** Nothing gains a column; the new kind is persisted whole like every other.
|
||||
pub const PROTOCOL_VERSION: u32 = 6;
|
||||
|
||||
// Not `#[tokio::main]`: on Windows the SCM dispatcher takes over this thread and starts the runtime
|
||||
// itself, on its own thread, once the service actually begins. The runtime is built by whichever
|
||||
|
||||
Reference in New Issue
Block a user