feat(update): session ids, per-node serialization, progress relay (VS-77) #95

Merged
David merged 1 commit from feat/VS-77-update-session-progress into main 2026-07-01 03:03:36 +02:00
Owner

Server half of the VS-77 / VA-97 co-design. Together with vervain-agent VA-97 (PR #98) this fixes the confirmed root cause of the agent-side #VA-96 failure: the two proto crates had diverged, so encode_agent_update emitted a single 52-byte hash frame for both start and end. The agent decoded every start marker as an End (length-based discrimination), so no session opened and every block was dropped. This PR removes that frame and replaces it with an explicit, session-tagged protocol.

Wire change (lockstep with vervain-agent VA-97; byte-identical)

  • AgentUpdate (13) is kind-discriminated: Begin = u16 cmd | u8 0 | u32 session_id | u64 total; End = u16 cmd | u8 1 | u32 session_id | 48-byte sha384. The legacy session-less encode_agent_update / encode_agent_update_block are removed.
  • AgentUpdateBlock (14) gains a u32 session_id header.
  • New AgentUpdateProgress (32, agent -> server), decoded into PostAuthFrame; a new vervain-agent-proto::update module mirrors the agent crate.

Server behaviour

  • build_agent_update_frames stamps a per-transfer session_id (Begin also carries the total binary length).
  • AgentRegistry gains a per-node transfer guard (try_begin_update / end_update) plus a monotonic session-id source. The reactive AgentHash-mismatch path, the proactive pushAgentUpdate, and the scheduled auto-update sweep all reserve the slot before streaming, so concurrent producers coalesce instead of interleaving frames for one node (the interleave the agent could not associate with a session). The slot is released on a terminal progress event (restarting/failed) or on disconnect.
  • dispatch_binary handles AgentUpdateProgress: releases the guard on a terminal phase and publishes a ProgressEvent onto a new node-id-keyed broadcast bus (agent_progress, modeled on connectivity).
  • The browser control-session loop subscribes and emits {"action":"event","event":{"action":"agentupdate",nodeid,session,phase,received,total,detail}}, gated by the same lookup_node_groupid visibility check as nodeconnect. Best-effort (Lagged/Closed handled); never back-pressures the agent transfer.

Tests

just check (compile + clippy -D warnings + fmt) and the full workspace test suite pass. New: guard coalesces a second in-flight push; progress bus publish/subscribe; proto round-trips; the two update-frame tests updated for the new Begin/End layout.

Dependency

Co-designed with vervain-agent VA-97 (PR #98). The wire layout must match byte-for-byte; land them together.

Server half of the VS-77 / VA-97 co-design. Together with vervain-agent VA-97 (PR #98) this fixes the confirmed root cause of the agent-side #VA-96 failure: the two proto crates had diverged, so `encode_agent_update` emitted a single 52-byte hash frame for both start and end. The agent decoded every start marker as an End (length-based discrimination), so no session opened and every block was dropped. This PR removes that frame and replaces it with an explicit, session-tagged protocol. ## Wire change (lockstep with vervain-agent VA-97; byte-identical) - `AgentUpdate` (13) is kind-discriminated: Begin = `u16 cmd | u8 0 | u32 session_id | u64 total`; End = `u16 cmd | u8 1 | u32 session_id | 48-byte sha384`. The legacy session-less `encode_agent_update` / `encode_agent_update_block` are removed. - `AgentUpdateBlock` (14) gains a `u32 session_id` header. - New `AgentUpdateProgress` (32, agent -> server), decoded into `PostAuthFrame`; a new `vervain-agent-proto::update` module mirrors the agent crate. ## Server behaviour - `build_agent_update_frames` stamps a per-transfer `session_id` (Begin also carries the total binary length). - `AgentRegistry` gains a per-node transfer guard (`try_begin_update` / `end_update`) plus a monotonic session-id source. The reactive AgentHash-mismatch path, the proactive `pushAgentUpdate`, and the scheduled auto-update sweep all reserve the slot before streaming, so concurrent producers coalesce instead of interleaving frames for one node (the interleave the agent could not associate with a session). The slot is released on a terminal progress event (restarting/failed) or on disconnect. - `dispatch_binary` handles `AgentUpdateProgress`: releases the guard on a terminal phase and publishes a `ProgressEvent` onto a new node-id-keyed broadcast bus (`agent_progress`, modeled on `connectivity`). - The browser control-session loop subscribes and emits `{"action":"event","event":{"action":"agentupdate",nodeid,session,phase,received,total,detail}}`, gated by the same `lookup_node_groupid` visibility check as `nodeconnect`. Best-effort (Lagged/Closed handled); never back-pressures the agent transfer. ## Tests `just check` (compile + clippy -D warnings + fmt) and the full workspace test suite pass. New: guard coalesces a second in-flight push; progress bus publish/subscribe; proto round-trips; the two update-frame tests updated for the new Begin/End layout. ## Dependency Co-designed with vervain-agent VA-97 (PR #98). The wire layout must match byte-for-byte; land them together.
feat(update): session ids, per-node serialization, progress relay
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 1m36s
Create release / Create release from merged PR (pull_request) Has been skipped
dad08dbc3e
WIRE CHANGE (lockstep with vervain-agent VA-97; the agent crate `vervain-agent-protocol` MUST match byte-for-byte):

- AgentUpdate (opcode 13) is now an explicit kind-discriminated frame: Begin = `u16 cmd | u8 0 | u32 session_id | u64 total`, End = `u16 cmd | u8 1 | u32 session_id | 48-byte sha384`. The legacy session-less `encode_agent_update` / `encode_agent_update_block` are removed: they emitted the length-discriminated form the agent misread (every start marker decoded as an End), which is the root cause of the agent-side VA-96 failure.
- AgentUpdateBlock (opcode 14) gains a `u32 session_id` header.
- New AgentUpdateProgress (opcode 32, agent -> server) decoded into PostAuthFrame; mirrored in a new `vervain-agent-proto::update` module.

Server behaviour:

- build_agent_update_frames stamps a per-transfer session_id (Begin also carries the total binary length) via AgentUpdate::encode_begin/encode_end and AgentUpdateBlock::encode.
- AgentRegistry gains a per-node transfer guard (try_begin_update / end_update) and a monotonic session_id source. The reactive AgentHash-mismatch path, the proactive pushAgentUpdate, and the scheduled auto-update sweep all reserve the slot before streaming, so concurrent producers coalesce instead of interleaving frames for one node. This eliminates the on-wire interleave the agent could not associate with a session. The slot is released on a terminal progress event (restarting/failed) or on disconnect.
- dispatch_binary handles AgentUpdateProgress: it releases the per-node guard on a terminal phase and publishes a ProgressEvent onto a new node-id-keyed broadcast bus (agent_progress, modeled on connectivity).
- The browser control-session loop subscribes to the progress bus and emits `{"action":"event","event":{"action":"agentupdate",nodeid,session,phase,received,total,detail}}`, gated by the same lookup_node_groupid visibility check as nodeconnect. The relay is best-effort (Lagged/Closed handled) and never back-pressures the agent transfer.

This is the server half of the VS-77/VA-97 co-design that fixes the VA-96 root cause.

#VS-77

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
David merged commit 76793b249a into main 2026-07-01 03:03:36 +02:00
David deleted branch feat/VS-77-update-session-progress 2026-07-01 03:03:36 +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-server!95
No description provided.