fix(update): split End-without-Begin from hash failure; coalesce drop-block log spam (VA-96) #97

Merged
David merged 1 commit from fix/VA-96-self-update-no-session into main 2026-07-01 02:11:42 +02:00
Owner

Fixes the self-update failure tracked in #VA-96, where a server-initiated transfer was observed dropping thousands of AgentUpdateBlock frames with "no active session", then logging a phantom self-update payload failed hash check; staged file deleted for an End that arrived with no Begin.

What changed

  • Added EndOutcome::NoSession. An AgentUpdate End with no in-flight session no longer reuses HashMismatch; net.rs logs that no transfer was in progress and nothing was staged, instead of the misleading hash-failure/deleted-file message that implied a payload was received and verified.
  • Added BlockOutcome::NoSession and a DropBurst coalescer. Orphan blocks (no accepted Begin) are tallied (count, total bytes, first and last request_id) and summarized in one log line per burst, flushed on the next Begin/End/in-session block or at a 1024-block cap. This replaces the per-block warn! that flooded journald with thousands of identical lines in under a second.
  • Added a debug log of every inbound control opcode and frame length (AgentUpdateBlock excluded, since those are tallied by the coalescer). A Begin is 4 bytes and an End is 52, so this makes visible whether the server ever sends an accepted Begin.

Tests

  • end_without_begin_is_no_session_not_hash_mismatch: End with no session yields NoSession and creates no staged file.
  • real_hash_mismatch_still_deletes_staged_file: a genuine mismatch still yields HashMismatch and deletes the staged file.
  • block_without_session_returns_no_session: orphan block returns NoSession.
  • drop_burst_*: the coalescer accumulates first/last/count/bytes, is a no-op when empty, and auto-flushes at the cap to keep output bounded.

just pre-commit passes (fmt, clippy with --deny warnings, build, workspace lib tests).

Out of scope (still open under #VA-96)

Confirming the root cause of why a Begin is never accepted requires a field capture with the new opcode/frame-length diagnostics deployed (default verbosity will not show the debug line; bump RUST_LOG to include vervain_agent::net=debug). If that capture shows a server-side protocol mismatch, the server fix will be filed and linked as a separate issue. This PR makes the failure observable and non-destructive without masking the underlying defect.

Fixes the self-update failure tracked in #VA-96, where a server-initiated transfer was observed dropping thousands of `AgentUpdateBlock` frames with "no active session", then logging a phantom `self-update payload failed hash check; staged file deleted` for an `End` that arrived with no `Begin`. ## What changed - Added `EndOutcome::NoSession`. An `AgentUpdate` `End` with no in-flight session no longer reuses `HashMismatch`; `net.rs` logs that no transfer was in progress and nothing was staged, instead of the misleading hash-failure/deleted-file message that implied a payload was received and verified. - Added `BlockOutcome::NoSession` and a `DropBurst` coalescer. Orphan blocks (no accepted `Begin`) are tallied (count, total bytes, first and last `request_id`) and summarized in one log line per burst, flushed on the next `Begin`/`End`/in-session block or at a 1024-block cap. This replaces the per-block `warn!` that flooded journald with thousands of identical lines in under a second. - Added a debug log of every inbound control opcode and frame length (`AgentUpdateBlock` excluded, since those are tallied by the coalescer). A `Begin` is 4 bytes and an `End` is 52, so this makes visible whether the server ever sends an accepted `Begin`. ## Tests - `end_without_begin_is_no_session_not_hash_mismatch`: `End` with no session yields `NoSession` and creates no staged file. - `real_hash_mismatch_still_deletes_staged_file`: a genuine mismatch still yields `HashMismatch` and deletes the staged file. - `block_without_session_returns_no_session`: orphan block returns `NoSession`. - `drop_burst_*`: the coalescer accumulates first/last/count/bytes, is a no-op when empty, and auto-flushes at the cap to keep output bounded. `just pre-commit` passes (fmt, clippy with `--deny warnings`, build, workspace lib tests). ## Out of scope (still open under #VA-96) Confirming the root cause of why a `Begin` is never accepted requires a field capture with the new opcode/frame-length diagnostics deployed (default verbosity will not show the debug line; bump `RUST_LOG` to include `vervain_agent::net=debug`). If that capture shows a server-side protocol mismatch, the server fix will be filed and linked as a separate issue. This PR makes the failure observable and non-destructive without masking the underlying defect.
fix(update): split End-without-Begin from hash failure; coalesce drops
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 11m17s
Create release / Create release from merged PR (pull_request) Has been skipped
5e1fb80bd5
Add EndOutcome::NoSession so an AgentUpdate End with no in-flight session no longer reuses HashMismatch. net.rs now logs that no transfer was in progress and nothing was staged, instead of the misleading "self-update payload failed hash check; staged file deleted" which implied a download was received and failed verification.

Add BlockOutcome::NoSession and coalesce dropped AgentUpdateBlock frames into one summary per burst via DropBurst (count, total bytes, first and last request_id), flushed on the next Begin/End/in-session block or at a 1024-block cap. This replaces the per-block warn that flooded journald with thousands of identical lines in under a second when blocks arrive with no active session.

Log every inbound control opcode and frame length at debug (AgentUpdateBlock excluded, since those are tallied by the coalescer) so the shape of each AgentUpdate frame is visible. A Begin is 4 bytes and an End is 52, which is what determines whether the server ever sends an accepted Begin; the field capture that confirms the missing-Begin root cause is still outstanding.

Add unit tests: End-without-Begin yields NoSession and stages nothing; a real hash mismatch still yields HashMismatch and deletes the staged file; the drop coalescer accumulates first/last/count/bytes and auto-flushes at the cap to keep output bounded.

#VA-96

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
David merged commit 5d6757f17f into main 2026-07-01 02:11:42 +02:00
David deleted branch fix/VA-96-self-update-no-session 2026-07-01 02:11:42 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-agent!97
No description provided.