feat: add empty client spool reconciliation

This commit is contained in:
2026-09-06 07:03:08 +00:00
parent bcb31a7f4e
commit fb778ce3e2
5 changed files with 74 additions and 0 deletions
+31
View File
@@ -59,3 +59,34 @@ func Handshake(ctx context.Context, transport Transport, hello *rvboxv1.ClientHe
}
return Session{ID: welcome.GetSessionId(), Generation: welcome.GetSessionGeneration(), Protocol: proto.Clone(payload.GetSelectedProtocol()).(*rvboxv1.ProtocolVersion)}, nil
}
// Reconcile sends the complete retained client state after welcome. The caller
// must apply the returned result durably before accepting fresh dispatch.
func Reconcile(ctx context.Context, transport Transport, session Session, snapshot *rvboxv1.ReconcileSnapshot, limits agentproto.Limits) (*rvboxv1.ReconcileResult, error) {
if transport == nil || session.ID == "" || session.Generation == 0 || snapshot == nil {
return nil, ErrProtocolHandshake
}
request := &rvboxv1.AgentEnvelope{SessionId: session.ID, SessionGeneration: session.Generation, Payload: &rvboxv1.AgentEnvelope_ReconcileSnapshot{ReconcileSnapshot: snapshot}}
if err := agentproto.ValidateEnvelope(request, limits, rvboxv1.Platform_PLATFORM_WINDOWS); err != nil {
return nil, fmt.Errorf("validate reconciliation snapshot: %w", err)
}
encoded, err := proto.Marshal(request)
if err != nil {
return nil, err
}
if err := transport.Write(ctx, encoded); err != nil {
return nil, err
}
received, err := transport.Read(ctx)
if err != nil {
return nil, err
}
result, err := agentproto.DecodeEnvelope(received, limits, rvboxv1.Platform_PLATFORM_WINDOWS)
if err != nil {
return nil, fmt.Errorf("decode ReconcileResult: %w", err)
}
if result.GetSessionId() != session.ID || result.GetSessionGeneration() != session.Generation || result.GetReconcileResult() == nil {
return nil, ErrProtocolHandshake
}
return proto.Clone(result.GetReconcileResult()).(*rvboxv1.ReconcileResult), nil
}
+18
View File
@@ -46,6 +46,24 @@ func TestHandshakeRejectsNonWelcome_BH_SES_07(t *testing.T) {
}
}
func TestReconcileSnapshot_HP_SES_08(t *testing.T) {
t.Parallel()
result := &rvboxv1.AgentEnvelope{SessionId: "issued-session", SessionGeneration: 7, Payload: &rvboxv1.AgentEnvelope_ReconcileResult{ReconcileResult: &rvboxv1.ReconcileResult{}}}
encoded, err := proto.Marshal(result)
if err != nil {
t.Fatal(err)
}
transport := &fakeTransport{read: encoded}
reconciled, err := Reconcile(context.Background(), transport, Session{ID: "issued-session", Generation: 7}, &rvboxv1.ReconcileSnapshot{}, agentproto.DefaultLimits())
if err != nil || reconciled == nil {
t.Fatalf("Reconcile = %#v, %v", reconciled, err)
}
var sent rvboxv1.AgentEnvelope
if err := proto.Unmarshal(transport.written, &sent); err != nil || sent.GetReconcileSnapshot() == nil || sent.GetSessionId() != "issued-session" || sent.GetSessionGeneration() != 7 {
t.Fatalf("sent snapshot valid=%t session=%q generation=%d err=%v", sent.GetReconcileSnapshot() != nil, sent.GetSessionId(), sent.GetSessionGeneration(), err)
}
}
type fakeTransport struct {
written []byte
read []byte