package agent import ( "context" "errors" "time" rvboxv1 "github.com/rvbox/rvbox/gen/go/rvbox/v1" "github.com/rvbox/rvbox/internal/domain" ) var ErrInvalidReconcileResult = errors.New("invalid reconciliation result") // ReconcileStore is the narrowly scoped durable operation required after the // server has confirmed a terminal command. Keeping the interface small lets the // network loop remain independent of SQLite and of the Windows supervisor. type ReconcileStore interface { DiscardTerminal(context.Context, domain.UUID, time.Time) error } // ApplyReconcileResult commits server-authorized terminal discards before the // session starts new work. It returns the active command IDs that the caller // must first terminate through the supervisor; marking those terminal is not // safe until the owned process and output readers have stopped. func ApplyReconcileResult(ctx context.Context, store ReconcileStore, result *rvboxv1.ReconcileResult, now time.Time) ([]domain.UUID, error) { if store == nil || result == nil || now.IsZero() { return nil, ErrInvalidReconcileResult } seen := make(map[domain.UUID]struct{}, len(result.GetTerminateLocalIssueUuids())+len(result.GetDiscardLocalTerminalIssueUuids())) terminate := make([]domain.UUID, 0, len(result.GetTerminateLocalIssueUuids())) for _, set := range [][]string{result.GetTerminateLocalIssueUuids(), result.GetDiscardLocalTerminalIssueUuids()} { for _, text := range set { issue, err := domain.ParseUUIDv7(text) if err != nil { return nil, ErrInvalidReconcileResult } if _, duplicate := seen[issue]; duplicate { return nil, ErrInvalidReconcileResult } seen[issue] = struct{}{} } } for _, text := range result.GetDiscardLocalTerminalIssueUuids() { issue, _ := domain.ParseUUIDv7(text) if err := store.DiscardTerminal(ctx, issue, now); err != nil { return nil, err } } for _, text := range result.GetTerminateLocalIssueUuids() { issue, _ := domain.ParseUUIDv7(text) terminate = append(terminate, issue) } return terminate, nil }