Narrow locality work inside the existing TargetRuntime. No new abstraction, no file moved, no lifecycle machinery.
Target-local accounting was dispersed across the managed driver:
the four event counter increments were written inline in publish_client_events, each spelled self.targets[index].counters.<field> = self.targets[index].counters.<field>.saturating_add(1);
the authoritative packets_sent was copied out of the underlying client at fifteen separate call sites, all spelled identically;
ManagedTargetOutcome was constructed twice from the same six counter fields — once in a free target_outcome function, once inline in finish_target.
Now:
TargetCounters::observe(&ClientEvent) owns per-event accounting and matches ClientEvent exhaustively, so a new event variant has to be classified deliberately rather than silently ignored by a wildcard.
TargetRuntime::sync_packets_sent(&AsyncClient) (and record_packets_sent for the send path, which already has the count in hand) own adopting the authoritative sent count.
TargetRuntime::outcome(end_reason, cleanup_failure) owns outcome construction; the free function and the inline copy are gone.
No counter field is read or written outside TargetRuntime/TargetCounters any more.
Behavior
No behavior change. Every existing managed test passes unchanged, including the outcome-counter tests.
replies_received semantics are preserved exactly — on-time unique echo replies only, with a late unique reply counted by late — and are now documented on both the private counter and the public ManagedTargetOutcome field, which previously had no documentation at all.
ManagedTargetOutcome's doc now states explicitly that these counters are durable, in contrast to the lossy ManagedEvent presentation stream.
Scope
Explicitly not changed:
No TimeoutScheduler was extracted.
No callback, effect, or lifecycle interface was introduced.
TargetState was not redesigned.
Durable counters were not replaced by irtt-stats, and public outcome semantics are unchanged.
No file was split, and TargetRuntime was not moved.
cargo check -p irtt-cli --no-default-features --features server --all-targets --locked
git diff --check
Two focused tests cover the new accounting seam: every event kind accounted exactly once with a late unique reply counted as late rather than received, and events with no durable target-local count leaving the counters untouched.
## Summary
Narrow locality work inside the existing `TargetRuntime`. No new abstraction, no file moved, no lifecycle machinery.
Target-local accounting was dispersed across the managed driver:
- the four event counter increments were written inline in `publish_client_events`, each spelled `self.targets[index].counters.<field> = self.targets[index].counters.<field>.saturating_add(1)`;
- the authoritative `packets_sent` was copied out of the underlying client at **fifteen** separate call sites, all spelled identically;
- `ManagedTargetOutcome` was constructed twice from the same six counter fields — once in a free `target_outcome` function, once inline in `finish_target`.
Now:
- `TargetCounters::observe(&ClientEvent)` owns per-event accounting and matches `ClientEvent` exhaustively, so a new event variant has to be classified deliberately rather than silently ignored by a wildcard.
- `TargetRuntime::sync_packets_sent(&AsyncClient)` (and `record_packets_sent` for the send path, which already has the count in hand) own adopting the authoritative sent count.
- `TargetRuntime::outcome(end_reason, cleanup_failure)` owns outcome construction; the free function and the inline copy are gone.
- No counter field is read or written outside `TargetRuntime`/`TargetCounters` any more.
## Behavior
- No behavior change. Every existing managed test passes unchanged, including the outcome-counter tests.
- `replies_received` semantics are preserved exactly — on-time unique echo replies only, with a late unique reply counted by `late` — and are now documented on both the private counter and the public `ManagedTargetOutcome` field, which previously had no documentation at all.
- `ManagedTargetOutcome`'s doc now states explicitly that these counters are durable, in contrast to the lossy `ManagedEvent` presentation stream.
## Scope
Explicitly not changed:
- No `TimeoutScheduler` was extracted.
- No callback, effect, or lifecycle interface was introduced.
- `TargetState` was not redesigned.
- Durable counters were not replaced by `irtt-stats`, and public outcome semantics are unchanged.
- No file was split, and `TargetRuntime` was not moved.
## Verification
Run from the task worktree, all successful:
- `cargo fmt --all -- --check`
- `cargo check --workspace --all-targets --all-features --locked`
- `cargo test --workspace --all-targets --all-features --locked`
- `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings`
- `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked`
- `cargo check -p irtt-cli --no-default-features --features client --all-targets --locked`
- `cargo check -p irtt-cli --no-default-features --features tui --all-targets --locked`
- `cargo check -p irtt-cli --no-default-features --features server --all-targets --locked`
- `git diff --check`
Two focused tests cover the new accounting seam: every event kind accounted exactly once with a late unique reply counted as late rather than received, and events with no durable target-local count leaving the counters untouched.
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:** `5506220f67`
<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>
Correct the timeout-only description of late replies
When replies arrive out of order but before either probe times out—for example, sequence 1 followed by sequence 0—process_echo_reply emits LateReply for sequence 0 because it is behind highest_received_seq. Consequently this outcome increments late and not replies_received despite arriving within its timeout, so the new public documentation incorrectly defines both counters in timeout-only terms and may lead callers to interpret reordered replies as timed-out probes.
Useful? React with 👍 / 👎.
**<sub><sub></sub></sub> Correct the timeout-only description of late replies**
When replies arrive out of order but before either probe times out—for example, sequence 1 followed by sequence 0—`process_echo_reply` emits `LateReply` for sequence 0 because it is behind `highest_received_seq`. Consequently this outcome increments `late` and not `replies_received` despite arriving within its timeout, so the new public documentation incorrectly defines both counters in timeout-only terms and may lead callers to interpret reordered replies as timed-out probes.
Useful? React with 👍 / 👎.
These tests directly instantiate the private TargetCounters, call its private observe helper, and mirror its match-arm results, even though outcome-level tests such as overdue_queued_reply_is_lost_then_late and post_deadline_receive_sweep_drains_queued_packets_and_is_bounded already verify the durable behavior through ManagedTargetOutcome. This couples a harmless private refactor to persistent test rewrites; remove these tests or replace them with normal-interface coverage only if an externally meaningful case remains unprotected.
**<sub><sub></sub></sub> Remove implementation-only counter tests**
These tests directly instantiate the private `TargetCounters`, call its private `observe` helper, and mirror its match-arm results, even though outcome-level tests such as `overdue_queued_reply_is_lost_then_late` and `post_deadline_receive_sweep_drains_queued_packets_and_is_bounded` already verify the durable behavior through `ManagedTargetOutcome`. This couples a harmless private refactor to persistent test rewrites; remove these tests or replace them with normal-interface coverage only if an externally meaningful case remains unprotected.
AGENTS.md reference: [AGENTS.md:L93-L103](https://github.com/Lochnair/irtt-rs/blob/5506220f67af4385a9cdab50d5c720cff610d11e/AGENTS.md#L93-L103)
Useful? React with 👍 / 👎.
Agreed — removed both tests and the helper; confirmed overdue_queued_reply_is_lost_then_late already asserts replies_received == 0 and late == 1 on the outcome, so this was duplicate coverage through private internals.
Agreed — removed both tests and the helper; confirmed `overdue_queued_reply_is_lost_then_late` already asserts `replies_received == 0` and `late == 1` on the outcome, so this was duplicate coverage through private internals.
_🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
Correct — a reply behind highest_received_seq is classified late by process_echo_reply while still pending, so both counters are now documented in terms of order and timeout rather than timeout alone.
Correct — a reply behind `highest_received_seq` is classified late by `process_echo_reply` while still pending, so both counters are now documented in terms of order and timeout rather than timeout alone.
_🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Narrow locality work inside the existing
TargetRuntime. No new abstraction, no file moved, no lifecycle machinery.Target-local accounting was dispersed across the managed driver:
publish_client_events, each spelledself.targets[index].counters.<field> = self.targets[index].counters.<field>.saturating_add(1);packets_sentwas copied out of the underlying client at fifteen separate call sites, all spelled identically;ManagedTargetOutcomewas constructed twice from the same six counter fields — once in a freetarget_outcomefunction, once inline infinish_target.Now:
TargetCounters::observe(&ClientEvent)owns per-event accounting and matchesClientEventexhaustively, so a new event variant has to be classified deliberately rather than silently ignored by a wildcard.TargetRuntime::sync_packets_sent(&AsyncClient)(andrecord_packets_sentfor the send path, which already has the count in hand) own adopting the authoritative sent count.TargetRuntime::outcome(end_reason, cleanup_failure)owns outcome construction; the free function and the inline copy are gone.TargetRuntime/TargetCountersany more.Behavior
replies_receivedsemantics are preserved exactly — on-time unique echo replies only, with a late unique reply counted bylate— and are now documented on both the private counter and the publicManagedTargetOutcomefield, which previously had no documentation at all.ManagedTargetOutcome's doc now states explicitly that these counters are durable, in contrast to the lossyManagedEventpresentation stream.Scope
Explicitly not changed:
TimeoutSchedulerwas extracted.TargetStatewas not redesigned.irtt-stats, and public outcome semantics are unchanged.TargetRuntimewas not moved.Verification
Run from the task worktree, all successful:
cargo fmt --all -- --checkcargo check --workspace --all-targets --all-features --lockedcargo test --workspace --all-targets --all-features --lockedcargo clippy --workspace --all-targets --all-features --locked -- -D warningsRUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --lockedcargo check -p irtt-cli --no-default-features --features client --all-targets --lockedcargo check -p irtt-cli --no-default-features --features tui --all-targets --lockedcargo check -p irtt-cli --no-default-features --features server --all-targets --lockedgit diff --checkTwo focused tests cover the new accounting seam: every event kind accounted exactly once with a late unique reply counted as late rather than received, and events with no durable target-local count leaving the counters untouched.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:
5506220f67ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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".
@@ -198,16 +198,32 @@ pub enum ManagedEndReason {}When replies arrive out of order but before either probe times out—for example, sequence 1 followed by sequence 0—
process_echo_replyemitsLateReplyfor sequence 0 because it is behindhighest_received_seq. Consequently this outcome incrementslateand notreplies_receiveddespite arriving within its timeout, so the new public documentation incorrectly defines both counters in timeout-only terms and may lead callers to interpret reordered replies as timed-out probes.Useful? React with 👍 / 👎.
These tests directly instantiate the private
TargetCounters, call its privateobservehelper, and mirror its match-arm results, even though outcome-level tests such asoverdue_queued_reply_is_lost_then_lateandpost_deadline_receive_sweep_drains_queued_packets_and_is_boundedalready verify the durable behavior throughManagedTargetOutcome. This couples a harmless private refactor to persistent test rewrites; remove these tests or replace them with normal-interface coverage only if an externally meaningful case remains unprotected.AGENTS.md reference: AGENTS.md:L93-L103
Useful? React with 👍 / 👎.
Agreed — removed both tests and the helper; confirmed
overdue_queued_reply_is_lost_then_latealready assertsreplies_received == 0andlate == 1on the outcome, so this was duplicate coverage through private internals.🤖 Addressed by Claude Code
@@ -198,16 +198,32 @@ pub enum ManagedEndReason {}Correct — a reply behind
highest_received_seqis classified late byprocess_echo_replywhile still pending, so both counters are now documented in terms of order and timeout rather than timeout alone.🤖 Addressed by Claude Code