Can’t expose custom field to REST API

I think the problem here is how you register the rest routes and how you return data for the single project endpoint.

When you register the route and the callback like this.

function single_project($data) {
    $post_ID = $data['id'];
    return get_post($post_ID);
}

add_action('rest_api_init', function () {
    register_rest_route( 'project/v1', 'post/(?P<id>\d+)', array(
        'methods' => 'GET',
        'callback' => 'single_project',
        'args' => ['id']
    ));
});

You’ll just get the default fields the WP_Postobject has. The data does not get processed the same way as regular posts do. This means that register_rest_field() does not have a chance to add the additional field to the response.

If you did something like

class WP_REST_Projects_Controller extends WP_REST_Posts_Controller {
    public function register_routes() {
        register_rest_route( 'project/v1', 'post/(?P<id>\d+)', array(
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => array( $this, 'get_item' ),
            'permission_callback' => array( $this, 'get_item_permissions_check' ),
            'args'                => [],
        ));
    }
}

add_action('rest_api_init', function () {
    (new WP_REST_Projects_Controller('project'))->register_routes();
});

then the additional field would get added to the response, as the global variable which register_rest_field() affects is used inside WP_REST_Controller which WP_REST_Posts_Controller extends. Inside get_additional_fields() method to be specific.

You should either extend the controller class and override it as needed or modify the single_project() callback to return additional fields along the post fields.