Utilising an existing page while using the “s” query parameter

Custom Query Var

Moving s to a custom query variable when a page is explicitly requested should be simple enough:

function wpse390935_set_page_search_qv( $wp ) {
  $qvs = $wp->query_vars;

  if( empty( $qvs['s'] ) || ( empty( $qvs['pagename'] ) && empty( $qvs['page_id'] ) ) )
    return;

  $wp->query_vars['page_search'] = $qvs['s'];
  unset( $wp->query_vars['s'] );
}
add_action( 'parse_query', 'wpse390935_set_page_search_qv' );

This prevents it from getting factored into the main query while, leaves it accessible later via get_query_var( 'page_search' ), and continues to present the variable as s to end-users.

Stashing the Query Var

If it absolutely needs to remain in s internally, then you could use a bit of a hack and remove s from the request’s query_vars using a parse_request action hook, then add it back again after the query executes.

I’ve used a singleton class here just to hold the s value in between hooks.

class WPSE390935_Search_Param_Passthrough {
  private static $instance = null;

  private $search="";

  public static function get_instance() {
    if( is_null( self::$instance ) )
      self::$instance = new self();

    return self::$instance;
  }

  private function __construct() {
    add_action( 'parse_request', [ $this, 'stash_query_var' ], 2 );
  }

  public function is_page_search() {
    if( is_admin() )
      return false;

    if( did_action( 'parse_request' ) > 0 )
      return ! empty( $this->search );
    
    throw new Error( 'Too early to determine is_page_search() condition.' );
  }

  public function restore_query_var() {
    set_query_var( 's', $this->search );
  }

  public function stash_query_var( $wp ) {
    $qvs = $wp->query_vars;

    if( ( empty( $qvs['pagename'] ) && empty( $qvs['page_id'] ) ) || empty( $qvs['s'] ) )
      return;
    
    $this->search = $qvs['s'];

    unset( $wp->query_vars['s'] );

    add_action( 'wp', [ $this, 'restore_query_var' ], 2 );
  }
}

function is_page_search() {
  return WPSE390935_Search_Param_Passthrough::get_instance()->is_page_search();
}

WPSE390935_Search_Param_Passthrough::get_instance();

I’d strongly recommend not adding the search value back to the main query, and instead just retrieving it from the class later when needed. This way the state of the main query’s query vars would not misrepresent what the main query actually is.

To a similar end, I’ve also provided an alternate conditional predicate in the form of is_page_search(). While you could set $wp_query->is_search = true in the restore_query_var() method, I would be concerned about any side-effects which that might have. Both in core and any plugin depending on the core predicate.