fix(client): timestamp probes after successful send #106

Merged
Lochnair merged 3 commits from fix/client-post-send-timestamp into main 2026-08-15 13:02:51 +00:00
Lochnair commented 2026-08-15 12:28:01 +00:00 (Migrated from github.com)

Summary

  • sent_at is now captured immediately after a successful UDP send returns, instead of before timeout finalization and the socket send.
  • Raw RTT and the userspace upstream-OWD fallback no longer include the socket send-call interval (or the small amount of local work before it).
  • The operational probe timeout deadline remains anchored to a pre-send timestamp, unchanged in behavior.
  • Linux kernel TX plausibility keeps a separate pre-send lower bound, so a valid TX_SOFTWARE timestamp generated while the send syscall is still in flight remains usable even though it can now legitimately precede the post-send sent_at.

Send transaction

New order (blocking and Tokio):

permission -> prepare -> machine preflight -> schedule preflight -> event reserve
send_anchor = ClientTimestamp::now()                 // PRIVATE pre-send anchor
finalize_probe_commit(preflight, send_anchor)?        // fallible: timeout checked_add, tx_not_before_wall
send_call_start = Instant::now()
socket.send(...) / try_send(...)
send_finished_at = Instant::now()
sent_at = ClientTimestamp::now()                      // PUBLIC post-send measurement
commit_probe_sent(commit, sent_at, bytes)              // infallible
schedule.commit(...)

finalize_probe_commit still performs the only fallible work in the transaction (timeout deadline checked_add, which can produce DurationOverflow) and still runs before the send. commit_probe_sent is now infallible and only runs after the send succeeded. This preserves the existing contract:

  • DurationOverflow still sends nothing and commits nothing (blocking_timeout_finalization_overflow_does_not_send_or_commit).
  • A Tokio WouldBlock try_send still samples no sent_at, commits nothing to the machine or schedule, and retains the PreparedProbe for retry; each retry gets a fresh send_anchor, and only a successful attempt's anchor/sent_at are retained.
  • Socket send failure (blocking or Tokio) still leaves the machine and schedule untouched.
  • The existing short-send fault-injection behavior (commit occurs before the presentation-length error) is unchanged; the retained sent_at in that case is the new post-send sample.

Timing semantics

  • sent_at (public): paired wall/monotonic ClientTimestamp captured immediately after the successful socket send. RTT endpoint, userspace upstream-OWD fallback endpoint, timer_error basis, and the value on EchoSent/EchoReply/EchoLoss/LateReply.
  • Pre-send anchor (private, send_anchor in the adapters / PendingProbe::tx_not_before_wall + timeout_at's basis in the machine): timeout deadline arithmetic and the kernel TX plausibility lower bound. Never exposed publicly.
  • send_call: unchanged — elapsed time strictly inside the send syscall (send_finished_at - send_call_start), excluding post-send timestamp-capture overhead.
  • timer_error: now abs(sent_at.mono - scheduled_at) using the post-send sent_at, describing deviation between scheduled send time and completed send, rather than time-before-syscall-entry.

Kernel TX interaction

  • Old lower bound: sent_at.wall <= kernel_tx <= received_at.wall (worked only because sent_at was pre-send).
  • New lower bound: tx_not_before_wall <= kernel_tx <= received_at.wall, where tx_not_before_wall is the pre-send anchor's wall sample.
  • Fallback (no/implausible kernel TX): now the post-send sent_at.wall instead of the pre-send sample.
  • New regression test kernel_tx_accepted_even_though_earlier_than_post_send_sent_at: pre-send anchor=1000ms, kernel TX=1005ms, post-send sent_at=1008ms, server recv=1020ms, client recv=1040ms → kernel TX is accepted and client_to_server == 15ms (not rejected for preceding the post-send sample, and not silently falling back to 1020-1008=12ms).
  • New test kernel_tx_below_pre_send_anchor_falls_back_to_post_send_not_pre_send: kernel TX below even the pre-send anchor (999ms) is rejected and falls back to the post-send sample (1008ms → 12ms), not the pre-send anchor (which would give 20ms).

RTT

  • Before: raw RTT = received.mono - pre_send.mono (included send-call interval).
  • After: raw RTT = received.mono - post_send_sent_at.mono.
  • New deterministic test raw_rtt_uses_post_send_sent_at_not_pre_send_anchor pins raw == 50ms for a post-send sample 10ms later than what a pre-send anchor would have given (60ms).
  • Adjusted/effective RTT inherit the new raw endpoint unchanged; server-processing subtraction is untouched.

Timeout

timeout_at is deliberately not derived from the new sent_at.mono. It remains pre_send_anchor.mono + probe_timeout, exactly as before this change in effect (just renamed/relocated internally). EchoLoss.sent_at reports the post-send measurement while the loss itself still fires at the pre-send-anchored deadline — pinned explicitly in blocking_probe_separates_permission_committed_send_and_send_call_timing.

Compatibility

  • No public type/field-shape changes. ClientEvent, RttSample, OneWayDelaySample, PendingProbe's public API surface via events are unchanged in shape; only the semantics of existing sent_at fields move from pre-send to post-send, documented in rustdoc.
  • Non-Linux platforms get the same RTT/OWD-fallback accuracy improvement (kernel TX is Linux/ancillary-only and was already optional).
  • Kernel TX remains the preferred upstream-OWD endpoint when locally plausible; nothing about TX capture, MSG_ERRQUEUE parsing, drain budget/locations, or the OPT_ID correlation invariant changed.
  • crates/irtt-server and Cargo.lock/Cargo.toml are untouched (empty diff).
  • Downstream (server_to_client) OWD and its kernel-RX-preferred selection are untouched.

Scope note: IPDV

crates/irtt-stats/src/normalization.rs's ReplySample::from_reply_parts feeds the public EchoReply/LateReply::sent_at directly into IpdvSample.client_send_mono / client_send_wall_ns, which crates/irtt-stats/src/ipdv.rs's send_ipdv_ns uses for the upstream/send-direction IPDV component. Since sent_at's meaning moves from pre-send to post-send, that IPDV component naturally shifts along with it (jitter is now measured between successive post-send samples instead of pre-send samples, differing by each probe's send-call-duration variance). Per the review scope, this is called out rather than compensated for — irtt-stats production code is untouched by this PR.

Scope

Out of scope, and not touched: Linux TX timestamp capture/correlation (SO_TIMESTAMPING flags, MSG_ERRQUEUE parsing, drain locations/budget, OPT_ID correlation), kernel RX handling, ProbeSchedule mathematics, timeout/loss classification policy, server timing (crates/irtt-server), irtt-stats production code/IPDV redesign, short-send policy redesign.

Verification

All run from the repo root:

  • cargo test -p irtt-client --all-features --locked — pass (341 unit tests + integration suites)
  • cargo test -p irtt-stats --all-features --locked — pass
  • cargo test -p irtt-cli --all-features --locked — pass
  • cargo fmt --all -- --check — pass
  • cargo check --workspace --all-targets --all-features --locked — pass
  • cargo test --workspace --all-targets --all-features --locked — pass
  • cargo clippy --workspace --all-targets --all-features --locked -- -D warnings — pass
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked — pass
  • cargo check -p irtt-cli --no-default-features --features client --all-targets --locked — pass
  • cargo check -p irtt-cli --no-default-features --features tui --all-targets --locked — pass
  • cargo check -p irtt-cli --no-default-features --features server --all-targets --locked — pass
  • git diff --check — pass
  • Cross-target cargo check -p irtt-client --all-features --locked --target <T> (via rustup rustc, RUSTC override) for x86_64-unknown-linux-gnu, aarch64-unknown-linux-musl, x86_64-unknown-freebsd, x86_64-pc-windows-gnu, i686-unknown-linux-gnu — all pass
## Summary - `sent_at` is now captured immediately after a successful UDP send returns, instead of before timeout finalization and the socket send. - Raw RTT and the userspace upstream-OWD fallback no longer include the socket send-call interval (or the small amount of local work before it). - The operational probe timeout deadline remains anchored to a pre-send timestamp, unchanged in behavior. - Linux kernel TX plausibility keeps a separate pre-send lower bound, so a valid `TX_SOFTWARE` timestamp generated while the send syscall is still in flight remains usable even though it can now legitimately precede the post-send `sent_at`. ## Send transaction New order (blocking and Tokio): ``` permission -> prepare -> machine preflight -> schedule preflight -> event reserve send_anchor = ClientTimestamp::now() // PRIVATE pre-send anchor finalize_probe_commit(preflight, send_anchor)? // fallible: timeout checked_add, tx_not_before_wall send_call_start = Instant::now() socket.send(...) / try_send(...) send_finished_at = Instant::now() sent_at = ClientTimestamp::now() // PUBLIC post-send measurement commit_probe_sent(commit, sent_at, bytes) // infallible schedule.commit(...) ``` `finalize_probe_commit` still performs the only fallible work in the transaction (timeout deadline `checked_add`, which can produce `DurationOverflow`) and still runs before the send. `commit_probe_sent` is now infallible and only runs after the send succeeded. This preserves the existing contract: - `DurationOverflow` still sends nothing and commits nothing (`blocking_timeout_finalization_overflow_does_not_send_or_commit`). - A Tokio `WouldBlock` `try_send` still samples no `sent_at`, commits nothing to the machine or schedule, and retains the `PreparedProbe` for retry; each retry gets a fresh `send_anchor`, and only a successful attempt's anchor/`sent_at` are retained. - Socket send failure (blocking or Tokio) still leaves the machine and schedule untouched. - The existing short-send fault-injection behavior (commit occurs before the presentation-length error) is unchanged; the retained `sent_at` in that case is the new post-send sample. ## Timing semantics - `sent_at` (public): paired wall/monotonic `ClientTimestamp` captured immediately after the successful socket send. RTT endpoint, userspace upstream-OWD fallback endpoint, `timer_error` basis, and the value on `EchoSent`/`EchoReply`/`EchoLoss`/`LateReply`. - Pre-send anchor (private, `send_anchor` in the adapters / `PendingProbe::tx_not_before_wall` + `timeout_at`'s basis in the machine): timeout deadline arithmetic and the kernel TX plausibility lower bound. Never exposed publicly. - `send_call`: unchanged — elapsed time strictly inside the send syscall (`send_finished_at - send_call_start`), excluding post-send timestamp-capture overhead. - `timer_error`: now `abs(sent_at.mono - scheduled_at)` using the post-send `sent_at`, describing deviation between scheduled send time and completed send, rather than time-before-syscall-entry. ## Kernel TX interaction - Old lower bound: `sent_at.wall <= kernel_tx <= received_at.wall` (worked only because `sent_at` was pre-send). - New lower bound: `tx_not_before_wall <= kernel_tx <= received_at.wall`, where `tx_not_before_wall` is the pre-send anchor's wall sample. - Fallback (no/implausible kernel TX): now the post-send `sent_at.wall` instead of the pre-send sample. - New regression test `kernel_tx_accepted_even_though_earlier_than_post_send_sent_at`: pre-send anchor=1000ms, kernel TX=1005ms, post-send `sent_at`=1008ms, server recv=1020ms, client recv=1040ms → kernel TX is accepted and `client_to_server == 15ms` (not rejected for preceding the post-send sample, and not silently falling back to `1020-1008=12ms`). - New test `kernel_tx_below_pre_send_anchor_falls_back_to_post_send_not_pre_send`: kernel TX below even the pre-send anchor (999ms) is rejected and falls back to the post-send sample (1008ms → 12ms), not the pre-send anchor (which would give 20ms). ## RTT - Before: `raw RTT = received.mono - pre_send.mono` (included send-call interval). - After: `raw RTT = received.mono - post_send_sent_at.mono`. - New deterministic test `raw_rtt_uses_post_send_sent_at_not_pre_send_anchor` pins `raw == 50ms` for a post-send sample 10ms later than what a pre-send anchor would have given (60ms). - Adjusted/effective RTT inherit the new raw endpoint unchanged; server-processing subtraction is untouched. ## Timeout `timeout_at` is deliberately **not** derived from the new `sent_at.mono`. It remains `pre_send_anchor.mono + probe_timeout`, exactly as before this change in effect (just renamed/relocated internally). `EchoLoss.sent_at` reports the post-send measurement while the loss itself still fires at the pre-send-anchored deadline — pinned explicitly in `blocking_probe_separates_permission_committed_send_and_send_call_timing`. ## Compatibility - No public type/field-shape changes. `ClientEvent`, `RttSample`, `OneWayDelaySample`, `PendingProbe`'s public API surface via events are unchanged in shape; only the *semantics* of existing `sent_at` fields move from pre-send to post-send, documented in rustdoc. - Non-Linux platforms get the same RTT/OWD-fallback accuracy improvement (kernel TX is Linux/`ancillary`-only and was already optional). - Kernel TX remains the preferred upstream-OWD endpoint when locally plausible; nothing about TX capture, `MSG_ERRQUEUE` parsing, drain budget/locations, or the `OPT_ID` correlation invariant changed. - `crates/irtt-server` and `Cargo.lock`/`Cargo.toml` are untouched (empty diff). - Downstream (`server_to_client`) OWD and its kernel-RX-preferred selection are untouched. ## Scope note: IPDV `crates/irtt-stats/src/normalization.rs`'s `ReplySample::from_reply_parts` feeds the public `EchoReply`/`LateReply::sent_at` directly into `IpdvSample.client_send_mono` / `client_send_wall_ns`, which `crates/irtt-stats/src/ipdv.rs`'s `send_ipdv_ns` uses for the upstream/send-direction IPDV component. Since `sent_at`'s meaning moves from pre-send to post-send, that IPDV component naturally shifts along with it (jitter is now measured between successive post-send samples instead of pre-send samples, differing by each probe's send-call-duration variance). Per the review scope, this is called out rather than compensated for — `irtt-stats` production code is untouched by this PR. ## Scope Out of scope, and not touched: Linux TX timestamp capture/correlation (`SO_TIMESTAMPING` flags, `MSG_ERRQUEUE` parsing, drain locations/budget, `OPT_ID` correlation), kernel RX handling, `ProbeSchedule` mathematics, timeout/loss classification policy, server timing (`crates/irtt-server`), `irtt-stats` production code/IPDV redesign, short-send policy redesign. ## Verification All run from the repo root: - `cargo test -p irtt-client --all-features --locked` — pass (341 unit tests + integration suites) - `cargo test -p irtt-stats --all-features --locked` — pass - `cargo test -p irtt-cli --all-features --locked` — pass - `cargo fmt --all -- --check` — pass - `cargo check --workspace --all-targets --all-features --locked` — pass - `cargo test --workspace --all-targets --all-features --locked` — pass - `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` — pass - `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked` — pass - `cargo check -p irtt-cli --no-default-features --features client --all-targets --locked` — pass - `cargo check -p irtt-cli --no-default-features --features tui --all-targets --locked` — pass - `cargo check -p irtt-cli --no-default-features --features server --all-targets --locked` — pass - `git diff --check` — pass - Cross-target `cargo check -p irtt-client --all-features --locked --target <T>` (via rustup rustc, `RUSTC` override) for `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-musl`, `x86_64-unknown-freebsd`, `x86_64-pc-windows-gnu`, `i686-unknown-linux-gnu` — all pass
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-08-15 12:32:32 +00:00
chatgpt-codex-connector[bot] (Migrated from github.com) left a comment

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3896376da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

### 💡 Codex Review Here are some automated review suggestions for this pull request. **Reviewed commit:** `a3896376da` <details> <summary>ℹ️ About Codex in GitHub</summary> <br/> [Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you - Open a pull request for review - Mark a draft as ready - Comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". </details>
@@ -96,33 +96,75 @@ socket.
## Retained state
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-15 12:32:33 +00:00

P2 Badge Correct the claim that IPDV is unaffected

When probe send-call durations vary, moving public sent_at from before the send to after it changes send-direction IPDV: ReplySample::from_reply_parts copies this value into client_send_mono/client_send_wall_ns, and send_ipdv_ns compares those fields across probes. The commit description acknowledges this semantic shift, so retaining “IPDV is likewise unaffected” as repository guidance establishes a false invariant; document the shift here, or preserve a separate pre-send timestamp if IPDV truly must remain unchanged.

AGENTS.md reference: crates/irtt-client/AGENTS.md:L153-L157

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Correct the claim that IPDV is unaffected** When probe send-call durations vary, moving public `sent_at` from before the send to after it changes send-direction IPDV: `ReplySample::from_reply_parts` copies this value into `client_send_mono`/`client_send_wall_ns`, and `send_ipdv_ns` compares those fields across probes. The commit description acknowledges this semantic shift, so retaining “IPDV is likewise unaffected” as repository guidance establishes a false invariant; document the shift here, or preserve a separate pre-send timestamp if IPDV truly must remain unchanged. AGENTS.md reference: [crates/irtt-client/AGENTS.md:L153-L157](https://github.com/Lochnair/irtt-rs/blob/a3896376da62585e35b31ece8d0edbe6d58a5a84/crates/irtt-client/AGENTS.md#L153-L157) Useful? React with 👍 / 👎.
Lochnair (Migrated from github.com) reviewed 2026-08-15 12:33:48 +00:00
@@ -96,33 +96,75 @@ socket.
## Retained state
Lochnair (Migrated from github.com) commented 2026-08-15 12:33:48 +00:00

Fixed — AGENTS.md now documents that the upstream IPDV component (send_ipdv_ns, fed by ReplySample.client_send_mono/client_send_wall_ns from sent_at) shifts endpoint along with sent_at itself, rather than claiming IPDV is unaffected. Pushed in e7b0632.

🤖 Addressed by Claude Code

Fixed — AGENTS.md now documents that the upstream IPDV component (`send_ipdv_ns`, fed by `ReplySample.client_send_mono`/`client_send_wall_ns` from `sent_at`) shifts endpoint along with `sent_at` itself, rather than claiming IPDV is unaffected. Pushed in e7b0632. _🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-08-15 12:39:01 +00:00
chatgpt-codex-connector[bot] (Migrated from github.com) left a comment

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7b0632239

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

### 💡 Codex Review Here are some automated review suggestions for this pull request. **Reviewed commit:** `e7b0632239` <details> <summary>ℹ️ About Codex in GitHub</summary> <br/> [Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you - Open a pull request for review - Mark a draft as ready - Comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". </details>
@@ -96,33 +96,75 @@ socket.
## Retained state
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-15 12:39:01 +00:00

P2 Badge Remove sent_at from downstream-delay guidance

sent_at is not an input to the downstream (server_to_client) one-way delay: compute_one_way derives that direction from meta.preferred_receive_wall(received_at.wall) and the server send timestamp. Retaining this statement in repository guidance establishes a false timing invariant and can cause future changes or tests to couple downstream delay to the probe's send endpoint; document sent_at as the RTT and upstream-fallback endpoint instead.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Remove sent_at from downstream-delay guidance** `sent_at` is not an input to the downstream (`server_to_client`) one-way delay: `compute_one_way` derives that direction from `meta.preferred_receive_wall(received_at.wall)` and the server send timestamp. Retaining this statement in repository guidance establishes a false timing invariant and can cause future changes or tests to couple downstream delay to the probe's send endpoint; document `sent_at` as the RTT and upstream-fallback endpoint instead. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-15 12:39:01 +00:00

P2 Badge Preserve event ordering with pre-send timeout deadlines

When a successful send takes longer than the configured probe_timeout—which may be any nonzero Duration—this stores a pre-send timeout_at that is earlier than the post-send sent_at.mono. Normal processing can therefore emit EchoSent(at = sent_at) followed by EchoLoss(at = timeout_at) with decreasing timestamps; irtt-stats/src/rolling.rs::push assumes its time-event deque is timestamp-ordered and stops eviction at the first newer front entry, so an expired loss behind that entry remains in rolling-time snapshots too long. Account for this reversed ordering in the event or rolling-window semantics while retaining the required pre-send overflow check.

AGENTS.md reference: crates/irtt-client/AGENTS.md:L117-L127

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Preserve event ordering with pre-send timeout deadlines** When a successful send takes longer than the configured `probe_timeout`—which may be any nonzero `Duration`—this stores a pre-send `timeout_at` that is earlier than the post-send `sent_at.mono`. Normal processing can therefore emit `EchoSent(at = sent_at)` followed by `EchoLoss(at = timeout_at)` with decreasing timestamps; `irtt-stats/src/rolling.rs::push` assumes its time-event deque is timestamp-ordered and stops eviction at the first newer front entry, so an expired loss behind that entry remains in rolling-time snapshots too long. Account for this reversed ordering in the event or rolling-window semantics while retaining the required pre-send overflow check. AGENTS.md reference: [crates/irtt-client/AGENTS.md:L117-L127](https://github.com/Lochnair/irtt-rs/blob/e7b06322396ca837079a7b59dc601299c736fe28/crates/irtt-client/AGENTS.md#L117-L127) Useful? React with 👍 / 👎.
Lochnair (Migrated from github.com) reviewed 2026-08-15 12:46:32 +00:00
Lochnair (Migrated from github.com) commented 2026-08-15 12:46:32 +00:00

Fixed — normalize_event now clamps StatsEvent::Loss's windowing timestamp to max(timeout_at, sent_at.mono) so rolling-window eviction's non-decreasing-order assumption holds, without changing the public EchoLoss.timeout_at field or the pre-send timeout semantics. Added a targeted unit test (loss_windowing_timestamp_is_clamped_to_its_own_sent_at) pinning this. Pushed in aac5247.

🤖 Addressed by Claude Code

Fixed — `normalize_event` now clamps `StatsEvent::Loss`'s windowing timestamp to `max(timeout_at, sent_at.mono)` so rolling-window eviction's non-decreasing-order assumption holds, without changing the public `EchoLoss.timeout_at` field or the pre-send timeout semantics. Added a targeted unit test (`loss_windowing_timestamp_is_clamped_to_its_own_sent_at`) pinning this. Pushed in aac5247. _🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
Lochnair (Migrated from github.com) reviewed 2026-08-15 12:46:39 +00:00
@@ -96,33 +96,75 @@ socket.
## Retained state
Lochnair (Migrated from github.com) commented 2026-08-15 12:46:39 +00:00

Fixed — reworded to state sent_at is the RTT endpoint and the upstream (client_to_server) OWD userspace fallback only; downstream (server_to_client) OWD is called out explicitly as unaffected, derived solely from ReceiveMeta::preferred_receive_wall and the negotiated server send timestamp. Pushed in aac5247.

🤖 Addressed by Claude Code

Fixed — reworded to state sent_at is the RTT endpoint and the upstream (client_to_server) OWD userspace fallback only; downstream (server_to_client) OWD is called out explicitly as unaffected, derived solely from ReceiveMeta::preferred_receive_wall and the negotiated server send timestamp. Pushed in aac5247. _🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
Sign in to join this conversation.