Skip to content

UCIe · Module 25

The Streaming Protocol

Tracing one Streaming object end to end in an interview — what state exists at each hop, why counting a held valid as a transfer duplicates allocations and leaks credit, why an identity freed a few cycles early corrupts a different transaction, and how to verify exactly-once delivery under backpressure and retry.

Chapter 25.3 walked the stack. This one walks a single object through it — and the interviewer's real test is whether you can hold the state at every hop without losing track of what has actually been accepted.

1. What They Ask

"Explain UCIe Streaming end-to-end."

Or: "Trace a transaction through Streaming." · "What happens when the receiver stalls?" · "How do you know an object was delivered exactly once?"

This is an advanced question and it is not a definition question. They want a trace, and every hop is an opportunity to reveal whether you know what state lives there.

2. The One-Sentence Model

Streaming is the protocol path for traffic that is not one of the natively mapped protocols — a generic, payload-oriented way to carry your own protocol over the UCIe die-to-die machinery.

And the sentence that follows it in an interview: "So the semantics stay yours; what UCIe supplies is the transport underneath." — which is 25.3 §2's layering claim applied to a specific case.

3. What They Are Really Testing

They are checkingThe tell
can you trace one object end to end?you name the state at each hop, not just the hops
do you distinguish acceptance from offering?§8 — the single biggest tell in this chapter
do you distinguish acceptance from delivery?you do not say "sent" when you mean "completed"
can you reason about backpressure?you treat a stall as normal, not as an error (§13)
do you preserve identity across retry?25.3 §10's three identities
can you discuss reliability without inventing a format?you talk about ownership, not flit layouts
could you verify it?§17 — and this is where seniors separate

And the second row is worth stating plainly: more candidates lose this question by confusing valid with a transfer than by anything else. §8 and §9 exist for that one mistake.

4. What You Can Safely Assert

5. The Answer Ladder

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
15 SECONDS — identification.
 
  "Streaming is the path for protocols that aren't natively mapped. PCIe
   and CXL have native mappings; if you're carrying something else, you
   use Streaming — your semantics stay yours, and UCIe provides the
   die-to-die transport underneath."
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
60 SECONDS — adds the trace and the boundary.
 
  "Streaming is the generic protocol path — for traffic that isn't PCIe or
   CXL. Your protocol keeps its own semantics; what you get from UCIe is
   the machinery underneath: the adapter framing objects, adding a CRC,
   and optionally doing link-level retry.
 
   End to end: a producer offers an object at the protocol boundary. It's
   accepted — and acceptance is the event that matters, not the offer.
   Once accepted, the sender has to hold that object until the transport
   contract says it can let go. The adapter turns it into something it can
   send, the physical layer moves it, the peer accepts it, and eventually
   it's delivered.
 
   The subtle part is that acceptance, transmission and delivery are three
   different events, and a retry sits between the second and the third
   without adding a new transaction."
 
  [STOP — that last clause is a hook, §21.]

Three properties.

The 60-second version does the whole job: what it is, why, the trace, and the trap. It is deliberately not a list of hops.

"Acceptance is the event that matters, not the offer" appears in the 60-second answer on purpose — it is the chapter's core, and saying it early means the follow-up is about something you know cold.

And the closing clause is a planted hook (25.1 §12): "a retry sits between the second and the third without adding a new transaction" invites "how do you make sure of that?", which is §12.

6. The End-to-End Trace

The three-minute answer, as a table you can reconstruct at a whiteboard.

#HopEventState createdState destroyed
1produceroffers an object
2protocol boundaryACCEPTSvalid && readyoutstanding entry, id allocated
3senderretains payload + identitynothing yet
4Adapterframes it into a transport objectobject entry
5Adapteradds header + CRC
6PHYattempt 1attempt state
7linkintegrity check failsattempt 1
8PHYattempt 2 — retryattempt state
9peer Adapterintegrity passes; accepts the objectpeer-side entry
10peerDELIVERS once to its protocol layerdelivery record
11senderlearns it may retireoutstanding entry, id freed

Five readings, and each is a follow-up you can pre-empt.

Row 2 is the only row that creates the outstanding entry. Not row 1. The offer created nothing (§8).

Rows 6–8 show one object and two attempts25.3 §10's identity hierarchy, and row 10 is still a single delivery.

Row 3 is the one candidates skip. "The sender retains the payload" is not obvious and is essential: it is why §15's payload-mutation bug is fatal.

Row 11 is the retirement, and the gap between rows 2 and 11 is the object's whole life — which is exactly the window §12's identity-reuse bug violates.

And nothing in this table names a signal or a field. It is a state trace, which is what makes it safe (§4) and what makes it useful.

7. RTL — The Stream Item

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE ONLY. A generic streaming object. Deliberately payload-oriented
// and protocol-agnostic — the point of Streaming is that YOUR semantics ride
// on top, so this struct carries a token, not a meaning (§2).
typedef struct packed {
  logic [ID_W-1:0]    id;          // sender-local identity for this object
  logic [GEN_W-1:0]   generation;  // WHICH USE of this id — §12
  logic [LEN_W-1:0]   length;      // in whatever unit the design defines
  logic [CLASS_W-1:0] class_id;    // traffic class, if the design has them
  logic [EPOCH_W-1:0] cfg_epoch;   // which configuration it was accepted under
} stream_item_t;

Architecture. A minimal descriptor: an identity, a generation, a size, a class and a configuration context. The payload itself lives in a buffer the descriptor refers to, because a struct carrying data does not survive contact with a real datapath.

State. One entry per outstanding object in the sender's table (§11).

Event. Constructed at acceptance (§6 row 2), not at offer.

Contract. generation is what makes an identity reusable safely (§12). cfg_epoch is what lets a late response be attributed to the configuration it was issued under (21.6 §11).

Failure. Without generation, §12's corruption. Without cfg_epoch, a straggler arriving after a reconfiguration is interpreted under rules that did not apply when it was sent.

DV/debug. These five fields are what a monitor emits and what a scoreboard correlates on (20.4) — and id alone is not enough, which is §12's whole point.

8. The Flagship Trap — Acceptance Is Not Offering

An offer is a request to transfer. An acceptance is a transfer. In a generic valid/ready model, valid alone means "I would like to send this"; only valid && ready means it happened.

And a producer holds valid high across a stall, which is normal, correct, and the source of the most common bug in this chapter.

If you allocate onThen during a 4-cycle stall you
validallocate 4 times for one object
valid && readyallocate once

Three consequences of the wrong choice, and being able to name all three is what makes this a senior answer:

Duplicate allocations — four outstanding entries for one object, so the table fills with phantoms.

Credit leak — four credits consumed, one returned (25.6 develops this), so the sender starves permanently.

And duplicate semantic operations, if the object is issued each time — the peer executes one request several times, which is a correctness failure rather than a performance one.

9. Wrong RTL — Counting Valid as Accepted

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — allocate on the offer. Plausible, and it is the single most common
// mistake at this boundary.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) outstanding_q <= '0;
  else if (in_valid) begin                    // <-- no `&& in_ready`
    outstanding_q          <= outstanding_q + 1'b1;
    table_q[alloc_ptr_q]   <= in_item;
    alloc_ptr_q            <= alloc_ptr_q + 1'b1;
    credit_q               <= credit_q - 1'b1;
  end
end

Worked, one object, four-cycle stall:

Cyclein_validin_readyWhat actually happenedWhat the RTL did
10010producer offersallocates, −1 credit
10110still offering, same objectallocates, −1 credit
10210still offeringallocates, −1 credit
10311the transfer happensallocates, −1 credit
1 object transferred4 entries, 4 credits gone

Four properties.

Three of the four entries are phantoms referring to an object that was transferred once. They will never be retired, because only one completion will ever arrive.

Credit is now permanently short by three — a leak with no leak in the credit logic (21.3 §14): the accounting is perfect and the event was wrong.

It scales with backpressure, so it is invisible on an idle link and appears under load — which is why it survives directed testing and shows up in the first real workload.

And if the design also issues on in_valid, the peer receives the object four times — the same one-character bug producing a correctness failure instead of a resource failure.

10. Corrected RTL — Qualify the Event

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECTED. One character of difference, and it is the whole chapter.
logic accept_fire;
assign accept_fire = in_valid && in_ready;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    outstanding_q <= '0;
    alloc_ptr_q   <= '0;
  end else begin
    // Outstanding as ONE signed next-state expression, so a same-cycle accept
    // and retire nets correctly instead of one update being lost.
    outstanding_q <= outstanding_q
                   + OUT_W'(accept_fire) - OUT_W'(retire_fire);
 
    if (accept_fire) begin
      table_q[alloc_ptr_q] <= in_item;
      alloc_ptr_q          <= alloc_ptr_q + 1'b1;
    end
  end
end
 
// MANDATORY. English: an outstanding entry is created only on a completed
// handshake. Sampled every cycle. Fires at cycle 100 of §9's trace — before
// any credit is lost.
a_alloc_only_on_accept: assert property (
  @(posedge clk) disable iff (!rst_n)
    $rose(outstanding_q) |-> $past(accept_fire)
);

Architecture. Acceptance as a named signal used everywhere, so the qualification cannot be forgotten in one of several places.

State. The outstanding count and the table.

Event. accept_fireand every consumer of the event uses that one signal, which is the structural defence: a design where three blocks each write in_valid && in_ready will eventually have one that does not.

Contract. outstanding_q is a single signed next-state expression, so an accept and a retire landing in the same cycle net to zero rather than one being lost to two sequential non-blocking assignments.

Failure. Two separate if statements writing outstanding_q lose the same-cycle pair, and the count drifts downward under load — a second, subtler version of the same class of bug.

DV/debug. The assertion fires at the first phantom allocation, in simulation, before any downstream symptom. In silicon the equivalent is an outstanding count that rises faster than the accepted-object counter (21.5 §20's offered-versus-accepted pair).

11. RTL — The Outstanding Table

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE ONLY (§7). What the sender holds between acceptance and
// retirement — §6's rows 2 through 11.
localparam int N_OUT = 32;
 
logic                    live_q     [N_OUT];
logic [GEN_W-1:0]        gen_q      [N_OUT];
stream_item_t            item_q     [N_OUT];
logic [EPOCH_W-1:0]      epoch_q    [N_OUT];
logic [ID_W-1:0]         id_of_slot [N_OUT];
 
// Allocation and retirement are INDEPENDENT events and may coincide.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    for (int i = 0; i < N_OUT; i++) begin
      live_q[i] <= 1'b0;
      gen_q[i]  <= '0;
    end
  end else begin
    // ALLOCATE on acceptance. The generation is bumped HERE, as the id is
    // handed out — not at retirement (§12).
    if (accept_fire) begin
      live_q[alloc_slot]     <= 1'b1;
      gen_q[alloc_slot]      <= gen_q[alloc_slot] + GEN_W'(1);
      item_q[alloc_slot]     <= in_item;
      epoch_q[alloc_slot]    <= cfg_epoch_q;
      id_of_slot[alloc_slot] <= in_item.id;
    end
 
    // RETIRE on an observed retirement event for a matching id AND generation.
    // Matching on id alone is §12.
    if (retire_fire
        && live_q[retire_slot]
        && (id_of_slot[retire_slot] == retire_id)
        && (gen_q[retire_slot]      == retire_gen))
      live_q[retire_slot] <= 1'b0;
  end
end
 
// MANDATORY. English: a slot is never allocated while already live.
// Fires on a double-allocation, which §9's bug produces immediately.
a_no_realloc_while_live: assert property (
  @(posedge clk) disable iff (!rst_n)
    (accept_fire && (alloc_slot == 0)) |-> !live_q[0]
);

Architecture. Per-slot liveness, generation, payload reference and configuration epoch — the four things needed to correlate a late event to the right operation.

State. N_OUT slots. Sized by the outstanding window the design needs, which is a throughput question (21.5 §26), not an arbitrary number.

Event. Allocation on accept_fire; retirement on a matched retirement event. Both can fire in the same cycle for different slots, which is why they are separate if statements writing different slots rather than a shared counter.

Contract. The generation is bumped at allocation, so it moves on even when a timeout — rather than a retirement — released the previous use. That is what makes a straggler detectable (§12).

Failure. Bumping the generation at retirement leaves a window where a timed-out slot and its reallocation share a generation — the same bug with extra steps.

DV/debug. live_q is the silicon-visible outstanding set (21.7 §21). A live count that never returns to zero at quiescence is §9's phantom allocation, visible from a register read.

12. Wrong RTL — Reusing an Identity Too Early

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the id is returned to the pool when the sender gives up locally,
// with no generation. Correct whenever responses cannot be late — which is
// exactly the assumption a retry breaks.
if (local_timeout_fire) id_free_q[timeout_id] <= 1'b1;   // <-- the bug
CycleEvent
1,000object id 5 accepted and issued
1,000–4,000the peer is slow, or a retry is in progress
4,000local timeout; id 5 returned to the pool
4,010a new, unrelated object takes id 5
4,300the original retirement for id 5 arrives
4,300it matches the new object and retires it
the new object is retired without ever being delivered

Five properties.

It is silent. No assertion, no error, no link event — the new object simply disappears from the outstanding table while still in flight.

Every component behaved as designed. The timeout freed a resource; the allocator reused it; the table matched an id to a live entry. 21.4 §17's layer-local correctness producing a lost transaction.

Retry makes it likely, not unlikely: a retried object is exactly the object whose response is late.

The fix is the generation bump in §11, plus matching on both id and generation — and the timeout may still free the slot, because the generation has already moved on.

And this is 25.3 §10's identity lesson with a concrete cost. "A retry isn't a second operation" is the principle; this is what happens when the identity that carries the principle is recycled too soon.

13. Backpressure Is Not an Error

A stall means "not now." It does not mean "failed", and it does not mean "drop it."

During a stall, the sender mustBecause
hold valid assertedwithdrawing it mid-offer is a protocol error in most handshakes
hold the payload and every field stable§15 — this is the bug
not allocate§8
not re-issueit was never accepted
not treat it as a timeout candidate yetthe transfer has not happened

And the interview framing worth having ready: "Backpressure is the receiver exercising flow control, which is the mechanism working. The failure mode isn't the stall — it's what the sender does during it."

14. RTL — Holding Stable Under Stall

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE ONLY (§7). Correct producer behaviour during backpressure.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    out_valid_q <= 1'b0;
  end else if (!out_valid_q || in_ready) begin
    // Load a NEW item only when the current one has been accepted, or when
    // we are not currently offering. This single condition is what makes the
    // payload stable across a stall (§15).
    out_valid_q <= have_next_item;
    out_item_q  <= next_item;
  end
  // else: HOLD. No branch here writes out_item_q while an offer is pending.
end

Architecture. A producer register that reloads only on acceptance or when idle — so the payload cannot change under a pending offer.

State. The offered item and its valid bit.

Event. !out_valid_q || in_ready is the reload condition. The else is deliberately empty, and that absence is the correctness argument.

Contract. The consumer is entitled to assume that what it accepts is what was offered when valid first rose. Changing it mid-offer breaks a contract the consumer cannot defend against.

Failure. §15.

DV/debug. The property in §16 catches it directly, and it is one of the cheapest and highest-value assertions on any handshake.

15. Wrong RTL — Payload Mutating Under Stall

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the producer updates its output every cycle from an upstream source,
// regardless of whether the current offer has been accepted.
always_ff @(posedge clk) begin
  out_valid_q <= have_next_item;
  out_item_q  <= next_item;          // <-- unconditional
end
Cyclevalidreadyitem.id offeredConsumer sees
20010Astalled
20110Bstalled — the offer silently changed
20211Caccepts C
A and B were never sent and never reported

Four properties.

Two objects vanished without any error, drop counter or fault. The producer believes it offered three; the consumer received one.

The consumer cannot detect it. It saw one valid rise and one acceptance — a perfectly ordinary transfer. There is no observable at the boundary that distinguishes this from correct behaviour.

Its signature is a count mismatch far downstream: the sender's accepted count and the receiver's delivered count differ, with no lost-packet mechanism to blame (21.4 §18's first-divergence walk finds it).

And the fix is §14's reload condition — one qualified else, and the class of bug becomes unrepresentable.

16. Assertion Inventory

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Illustrative properties (§4) — not normative for UCIe.
 
// (1) PAYLOAD STABLE UNDER STALL.
// English: while an offer is pending and unaccepted, nothing about it changes.
// Sampled every cycle. Catches §15 at the first mutation — the cheapest
// high-value assertion on any handshake.
a_stable_while_stalled: assert property (
  @(posedge clk) disable iff (!rst_n)
    (out_valid_q && !in_ready) |=> ($stable(out_item_q) && out_valid_q)
);
 
// (2) ALLOCATION ONLY ON ACCEPTANCE.  [§10]
// Catches §9 at the first phantom allocation.
 
// (3) NO IDENTITY REUSE WHILE LIVE.
// English: an id is not handed out again until its previous use retires or
// its generation has advanced. Catches §12.
a_no_reuse_while_live: assert property (
  @(posedge clk) disable iff (!rst_n)
    (accept_fire && (in_item.id == TEST_ID))
      |-> (!live_q[slot_of(TEST_ID)] || (in_item.generation != gen_q[slot_of(TEST_ID)]))
);
 
// (4) RETRY DOES NOT DUPLICATE DELIVERY.
// English: however many physical attempts occur, one object is delivered once.
// Bounded by RETIREMENT — an observed event — rather than by an invented
// window, which is what makes it both terminating and meaningful (21.6 §29).
property p_deliver_once(int unsigned id, int unsigned g);
  @(posedge clk) disable iff (!rst_n)
    (deliver_fire && (deliver_id == id) && (deliver_gen == g))
      |=> !(deliver_fire && (deliver_id == id) && (deliver_gen == g))
          throughout (1'b1 [*1:$] ##0 (retire_fire && (retire_id == id)));
endproperty
 
// (5) A TRANSPORT ATTEMPT NEVER WRITES THE OBJECT IDENTITY.
// English: retries touch attempt state only. Structural non-interference —
// best discharged formally, because simulation shows only what it ran.
a_attempt_does_not_touch_identity: assert property (
  @(posedge clk) disable iff (!rst_n)
    retx_fire |=> ($stable(item_q[cur_slot].id) && $stable(gen_q[cur_slot]))
);

Architecture. Two handshake properties, one identity-lifetime property, one exactly-once property, one structural.

Sampled timing. Property (1) uses |=> so it compares the stalled cycle against the next cycle's payload — the correct phase for a registered output. Property (4) is bounded by an observed retirement; an unbounded never can only fail and never reports a pass (21.6 §29).

Contract. Property (4) requires that retirement is guaranteed to occur, or it is vacuously true forever. That guarantee must be written alongside it, and saying so in an interview is a strong move.

Failure if omitted. Without (1), §15 ships and presents as an unexplained count mismatch. Without (3), §12's silent loss ships, and it is the hardest of the four to find.

DV/debug. Property (5) belongs in a formal flow. Non-interference is a proof obligation — and "that one I'd want to prove rather than simulate" is exactly the sentence 25.3 §17 recommends having ready.

17. "How Would You Verify Streaming?"

The standard senior follow-up, and it is really asking whether you can structure a verification plan.

ComponentJobModule
input monitoremit one transaction per acceptance20.2 — and §9 is why this matters
semantic modelwhat the protocol above expects, independent of the DUT20.4 §17
transport modelobjects and attempts, tracked separately25.3 §11
identity correlationjoin by id + generation, never by attempt§12
retry injectionforce retransmission deliberately§18's case
backpressure generationrandomised ready de-assertion§13
coveragestall length, retry count, id-reuse distance, simultaneous accept/retire20.5

Three properties worth saying aloud.

The monitor must emit on acceptance, not on validthe same bug as §9, in the testbench, and it makes the scoreboard report duplicates that never happened (21.6 §23).

The models must be independent of the DUT. If the scoreboard calls a DUT function to decide what should have happened, a shared misunderstanding is invisible (21.6 §24).

And the coverage row is where seniors distinguish themselves. "Stall length crossed with retry count" is the cross that finds §18 — and naming a cross rather than a coverpoint shows you know how these bugs actually surface.

18. Flagship Debug Case

Symptom: one transaction is delivered twice, but only under sustained backpressure with retries active.

StepObservation
1duplicate delivery reported by the peer's protocol layer
2only under load; clean on a quiet link
3link counters clean; CRC clean; no errors logged
4hypothesis A — the retry mechanism is duplicating
5hypothesis B — the peer's de-duplication is broken
6hypothesis C — the sender issued it twice
7discriminating observation: does the sender's accepted count match its issued count?
8it does not — issued exceeds accepted by the number of stalled cycles
9first divergence: allocation at cycle 100, on valid with ready low (§9)
10root cause: the acceptance event was never qualified
11fix: §10's accept_fire
12prevention: §16 property (2), plus a stall-length × retry-count cover cross

Four readings.

Hypotheses A and B blame the transport and the peer, and both are innocent — which is why step 7 matters: it is a local check that eliminates two remote hypotheses at once.

Step 8's signature is diagnostic on its own. Issued exceeding accepted by the stall duration is not a subtle correlation; it names the mechanism.

Retry was a trigger, not a cause. It made the object take longer, which lengthened the stall, which multiplied the phantom allocations — so "it only happens with retries on" was a misleading clue.

And this is the interview answer's shape: symptom → hypotheses → one discriminating observation → first divergence → fix → prevention (21.6 §30). Walking it in that order is what separates a debug answer from a guess.

19. Twelve Follow-Ups

Q1 — "What happens when ready drops?" Nothing is allocated, nothing is issued, and the payload must not change (§13, §15). The producer holds.

Q2 — "When do you allocate an id?" On acceptance, never on offer (§8). It is the single most common mistake at this boundary.

Q3 — "When can you reuse it?" After retirement — and even then, bump the generation (§11), because a straggler may still be in flight.

Q4 — "Does a retry create a new transaction?" No. One semantic object, one transport object, several attempts (25.3 §10).

Q5 — "How would you verify exactly-once delivery?" §16 property (4), bounded by an observed retirement rather than an invented window, plus a retirement-guaranteed property alongside it.

Q6 — "What state survives a recovery?" A scope question (25.3 §14). Retain or replay, agreed by both sides; silent drop is a hang with a healthy link.

Q7 — "How would you model outstanding work?" §11's table — liveness, generation, payload reference, configuration epoch — and an outstanding count maintained as one signed next-state expression.

Q8 — "Why isn't item_done() semantic completion in UVM?" Because it signals the driver is finished with the item, not that the DUT completed the operation. Treating it as completion retires transactions the design is still working on — and a scoreboard built on it under-reports outstanding work.

Q9 — "How do you avoid head-of-line blocking?" Separate queues per class, plus a reserve for progress-critical traffic (22.3 §13). Separate queues alone do not prevent credit exhaustion.

Q10 — "How does Streaming differ from a natively mapped protocol?" With PCIe or CXL the semantics are known and mapped; with Streaming your protocol keeps its own semantics and you carry them. And the revision detail is worth citing precisely: 1.0 supported Streaming Protocols only in Raw Mode; 1.1 permitted them on FDI (§4).

Q11 — "What belongs in the Adapter?" Framing, CRC, optionally retry, and arbitration if several protocols share the link (25.3 §9). Not semantics.

Q12 — "Where would you put coverage?" At the acceptance boundary and the delivery boundary, crossed — stall length × retry count × id-reuse distance (§17). The cross is what finds §18.

20. Bad Answers

The answerWhy it is weak
"Streaming just sends raw data."it says nothing about state, identity or ownership
"You decrement credit when you send."§8 — send is ambiguous; the event is acceptance
"Backpressure means the link failed."§13 — it is the mechanism working
"A retry means you send the transaction again."conflates a physical attempt with a semantic operation (§Q4)
[names Streaming signals or a flit layout]§4 — not inspected, and a confident error
"You can reuse the ID once you time out."§12 — the exact bug
"One scoreboard checks everything."§17 — semantic and transport need separate models

And the fifth row is the trap specific to this chapter. Streaming is the area where a candidate is most likely to reach for remembered detail — and the correct move is 25.3 §13's: describe the boundary and say you would confirm the encoding.

21. Controlling the Next Question

Close withInvitesWhich is
"…acceptance is the event that matters, not the offer.""what goes wrong if you get that wrong?"§9 — a complete worked answer
"…a retry sits in between without adding a transaction.""how do you guarantee that?"§16's exactly-once property
"…and the identity has to outlive the attempts.""what if it's reused early?"§12 — silent loss, a strong story
"…I'd verify it with separate semantic and transport models.""why separate?"21.6 §24 — independence

And the first row is the best hook in the chapter. It is one clause, unambiguously correct, and the follow-up lands you in §9's four-cycle table — which you can draw in twenty seconds and which demonstrates the trap, the consequence and the fix together.

22. Understanding Check

23. Summary

Five things.

Acceptance is the event; the offer is not (§8). One character of RTL, and getting it wrong produces phantom allocations, a credit leak with correct credit logic, and possibly duplicate operations.

Trace state, not hops (§6). Eleven rows, each naming what state is created or destroyed — that is the three-minute answer.

Identity must outlive the attempts (§12). A generation bumped at allocation is what stops a straggler retiring the wrong object.

Backpressure is the mechanism working (§13). The failure mode is what the sender does during it — and a payload that mutates under stall is undetectable at the boundary.

And verification needs separate semantic and transport models (§17), joined by identity — plus a stall-length × retry-count cross, which is what finds the flagship bug.