Repository: hashintel/hash
Pull request #6884: H-4347: Move logic to create system account to Graph
Author: login: TimDiekmann
Labels: name: area/apps > hash*
description: Affects HASH (a `hash-*` app)
name: area/apps > hash-api
description: Affects the HASH API (app)
name: area/libs
description: Relates to first-party libraries/crates/packages (area)
name: type/eng > backend
description: Owned by the @backend team
name: area/apps
Created: 2025-04-08T10:13:13Z | Merged: 2025-04-15T10:30:20Z

## 🌟 What is the purpose of this PR?

To allow creating bootsrapping permissions we need a way to create a system account from the graph. This moves the current logic to the graph.

## 🚫 Blocked by

- #6880 
- #6881 

## Pre-Merge Checklist 🚀

### 🚢 Has this modified a publishable library?

<!-- Confirm you have taken the necessary action to record a changeset or publish a change, as appropriate -->
<!-- Tick AT LEAST ONE box and delete the rest. Do not delete this section! see libs/README.md for info on publishing -->

This PR:

- [x] does not modify any publishable blocks or libraries, or modifications do not need publishing

### 📜 Does this require a change to the docs?

<!-- If this adds a user facing feature or modifies how an existing feature is used, it likely needs a docs change. -->
<!-- Tick ONE box and delete the rest. Do not delete this section! -->

The changes in this PR:

- [x] are internal and do not require a docs change

### 🕸️ Does this require a change to the Turbo Graph?

<!-- If this adds or moves an existing package, modifies `scripts` in a `package.json`, it likely needs a turbo graph change. -->
<!-- Tick ONE box and delete the rest. Do not delete this section! -->

The changes in this PR:

- [x] do not affect the execution graph


Diff:
diff --git a/apps/hash-api/src/graph/knowledge/system-types/user.ts b/apps/hash-api/src/graph/knowledge/system-types/user.ts
index 5121fede2f8..b508af69c11 100644
--- a/apps/hash-api/src/graph/knowledge/system-types/user.ts
+++ b/apps/hash-api/src/graph/knowledge/system-types/user.ts
@@ -245,7 +245,6 @@ export const createUser: ImpureGraphFunction<
     shortname?: string;
     displayName?: string;
     isInstanceAdmin?: boolean;
-    userAccountId?: ActorEntityUuid;
   },
   Promise<User>
 > = async (ctx, authentication, params) => {
@@ -289,34 +288,29 @@ export const createUser: ImpureGraphFunction<
 
   const userShouldHavePermissionsOnWeb = shortname && displayName;
 
-  let userAccountId: ActorEntityUuid;
-  if (params.userAccountId) {
-    userAccountId = params.userAccountId;
-  } else {
-    userAccountId = await createAccount(ctx, authentication, {
-      accountType: "user",
-    });
-
-    await createWeb(
-      ctx,
-      { actorId: systemAccountId },
-      {
-        webId: userAccountId as WebId,
-        owner: {
-          kind: "account",
-          /**
-           * Creating a web allows users to create further entities in it
-           * – we don't want them to do that until they've completed signup (have a shortname and display name)
-           * - the web is created with the system account as the owner and will be updated to the user account as the
-           *   owner once the user has completed signup
-           */
-          subjectId: userShouldHavePermissionsOnWeb
-            ? userAccountId
-            : systemAccountId,
-        },
+  const userAccountId = await createAccount(ctx, authentication, {
+    accountType: "user",
+  });
+
+  await createWeb(
+    ctx,
+    { actorId: systemAccountId },
+    {
+      webId: userAccountId as WebId,
+      owner: {
+        kind: "account",
+        /**
+         * Creating a web allows users to create further entities in it
+         * – we don't want them to do that until they've completed signup (have a shortname and display name)
+         * - the web is created with the system account as the owner and will be updated to the user account as the
+         *   owner once the user has completed signup
+         */
+        subjectId: userShouldHavePermissionsOnWeb
+          ? userAccountId
+          : systemAccountId,
       },
-    );
-  }
+    },
+  );
 
   const userWebMachineActorId = await createWebMachineActor(
     ctx,
diff --git a/apps/hash-api/src/graph/system-account.ts b/apps/hash-api/src/graph/system-account.ts
index 1ed7467329e..aef8f85f83e 100644
--- a/apps/hash-api/src/graph/system-account.ts
+++ b/apps/hash-api/src/graph/system-account.ts
@@ -1,17 +1,10 @@
-import type { ActorEntityUuid } from "@blockprotocol/type-system";
+import type { MachineId } from "@blockprotocol/type-system";
 import type { Logger } from "@local/hash-backend-utils/logger";
-import { publicUserAccountId } from "@local/hash-backend-utils/public-user-account-id";
-import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids";
-import { versionedUrlFromComponents } from "@local/hash-subgraph/type-system-patch";
 
-import { createAccount } from "./account-permission-management";
 import type { ImpureGraphContext } from "./context-types";
-import { getEntityTypeById } from "./ontology/primitive/entity-type";
 
 // eslint-disable-next-line import/no-mutable-exports
-export let systemAccountId: ActorEntityUuid;
-
-const authentication = { actorId: publicUserAccountId };
+export let systemAccountId: MachineId;
 
 /**
  * Ensure the `systemAccountId` exists by fetching it or creating it. Note this
@@ -23,29 +16,9 @@ export const ensureHashSystemAccountExists = async (params: {
 }) => {
   const { logger, context } = params;
 
-  try {
-    // The system account is the creator of the HASH system types
-    const orgEntityType = await getEntityTypeById(context, authentication, {
-      /**
-       * We want to use the first version of the `Organization` entity type,
-       * so that the system account can be determined in early migration
-       * steps where only the first version is available.
-       */
-      entityTypeId: versionedUrlFromComponents(
-        systemEntityTypes.organization.entityTypeBaseUrl,
-        1,
-      ),
-    });
-
-    systemAccountId = orgEntityType.metadata.provenance.edition.createdById;
-
-    logger.debug(`Using existing system account id: ${systemAccountId}`);
-  } catch {
-    // We don't have any system types yet, so we need to create the system account, which will create them
+  systemAccountId = await context.graphApi
+    .getOrCreateSystemAccount()
+    .then(({ data: machineId }) => machineId as MachineId);
 
-    systemAccountId = await createAccount(context, authentication, {
-      accountType: "machine",
-    });
-    logger.info(`Created system user account id: ${systemAccountId}`);
-  }
+  logger.info(`Using system account ${systemAccountId}`);
 };
diff --git a/libs/@local/graph/api/openapi/openapi.json b/libs/@local/graph/api/openapi/openapi.json
index 1bff7bffb7c..5d973231ed3 100644
--- a/libs/@local/graph/api/openapi/openapi.json
+++ b/libs/@local/graph/api/openapi/openapi.json
@@ -3040,6 +3040,30 @@
         }
       }
     },
+    "/system_account": {
+      "get": {
+        "tags": [
+          "Graph",
+          "Account"
+        ],
+        "operationId": "get_or_create_system_account",
+        "responses": {
+          "200": {
+            "description": "The schema of the created account",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MachineId"
+                }
+              }
+            }
+          },
+          "500": {
+            "description": "Store error occurred"
+          }
+        }
+      }
+    },
     "/webs": {
       "post": {
         "tags": [
diff --git a/libs/@local/graph/api/src/rest/account.rs b/libs/@local/graph/api/src/rest/account.rs
index 574e6a4876b..8cc5eb63dfb 100644
--- a/libs/@local/graph/api/src/rest/account.rs
+++ b/libs/@local/graph/api/src/rest/account.rs
@@ -13,6 +13,7 @@ use error_stack::ResultExt as _;
 use hash_graph_authorization::{
     AuthorizationApi as _, AuthorizationApiPool,
     backend::ModifyRelationshipOperation,
+    policies::store::PrincipalStore,
     schema::{
         AccountGroupAdministratorSubject, AccountGroupMemberSubject, AccountGroupPermission,
         AccountGroupRelationAndSubject, WebOwnerSubject,
@@ -30,7 +31,6 @@ use type_system::{
 };
 use utoipa::OpenApi;
 
-use super::api_resource::RoutedResource;
 use crate::rest::{
     AuthenticatedUserHeader, OpenApiQuery, PermissionResponse, QueryLogger, json::Json,
     status::report_to_response,
@@ -41,6 +41,7 @@ use crate::rest::{
     paths(
         create_account,
         create_account_group,
+        get_or_create_system_account,
 
         check_account_group_permission,
         add_account_group_member,
@@ -71,15 +72,17 @@ use crate::rest::{
 )]
 pub(crate) struct AccountResource;
 
-impl RoutedResource for AccountResource {
+impl AccountResource {
     /// Create routes for interacting with accounts.
-    fn routes<S, A>() -> Router
+    pub(crate) fn routes<S, A>() -> Router
     where
         S: StorePool + Send + Sync + 'static,
         A: AuthorizationApiPool + Send + Sync + 'static,
+        for<'p, 'a> S::Store<'p, A::Api<'a>>: PrincipalStore,
     {
         // TODO: The URL format here is preliminary and will have to change.
         Router::new()
+            .route("/system_account", get(get_or_create_system_account::<S, A>))
             .route("/accounts", post(create_account::<S, A>))
             .nest(
                 "/account_groups",
@@ -103,6 +106,47 @@ impl RoutedResource for AccountResource {
     }
 }
 
+#[utoipa::path(
+    get,
+    path = "/system_account",
+    tag = "Account",
+    responses(
+        (status = 200, content_type = "application/json", description = "The schema of the created account", body = MachineId),
+
+        (status = 500, description = "Store error occurred"),
+    )
+)]
+#[tracing::instrument(
+    level = "info",
+    skip(store_pool, authorization_api_pool, temporal_client)
+)]
+async fn get_or_create_system_account<S, A>(
+    authorization_api_pool: Extension<Arc<A>>,
+    temporal_client: Extension<Option<Arc<TemporalClient>>>,
+    store_pool: Extension<Arc<S>>,
+) -> Result<Json<MachineId>, Response>
+where
+    S: StorePool + Send + Sync,
+    A: AuthorizationApiPool + Send + Sync,
+    for<'p, 'a> S::Store<'p, A::Api<'a>>: PrincipalStore,
+{
+    let authorization_api = authorization_api_pool
+        .acquire()
+        .await
+        .map_err(report_to_response)?;
+
+    let mut store = store_pool
+        .acquire(authorization_api, temporal_client.0)
+        .await
+        .map_err(report_to_response)?;
+
+    store
+        .get_or_create_system_account()
+        .await
+        .map_err(report_to_response)
+        .map(Json)
+}
+
 #[utoipa::path(
     post,
     path = "/accounts",
diff --git a/libs/@local/graph/api/src/rest/api_resource.rs b/libs/@local/graph/api/src/rest/api_resource.rs
deleted file mode 100644
index 5eb10e1976d..00000000000
--- a/libs/@local/graph/api/src/rest/api_resource.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use axum::Router;
-use hash_graph_authorization::AuthorizationApiPool;
-use hash_graph_store::pool::StorePool;
-
-use crate::rest::RestApiStore;
-
-/// With REST, we define resources that can be acted on. These resources are defined through
-/// routes and HTTP methods.
-///
-/// This trait encapsulates a way to define related resource operations
-/// through a `Router`, making it explicitly clear we want to provide `OpenApi` specification as
-/// documentation for the routes.
-pub(crate) trait RoutedResource: utoipa::OpenApi {
-    fn routes<S, A>() -> Router
-    where
-        S: StorePool + Send + Sync + 'static,
-        A: AuthorizationApiPool + Send + Sync + 'static,
-        for<'pool> S::Store<'pool, A::Api<'pool>>: RestApiStore;
-
-    fn documentation() -> utoipa::openapi::OpenApi {
-        Self::openapi()
-    }
-}
diff --git a/libs/@local/graph/api/src/rest/data_type.rs b/libs/@local/graph/api/src/rest/data_type.rs
index eb91047a97b..36b45bf9f73 100644
--- a/libs/@local/graph/api/src/rest/data_type.rs
+++ b/libs/@local/graph/api/src/rest/data_type.rs
@@ -56,7 +56,6 @@ use type_system::{
 };
 use utoipa::{OpenApi, ToSchema};
 
-use super::api_resource::RoutedResource;
 use crate::rest::{
     AuthenticatedUserHeader, OpenApiQuery, PermissionResponse, QueryLogger, RestApiStore,
     json::Json,
@@ -126,9 +125,9 @@ use crate::rest::{
 )]
 pub(crate) struct DataTypeResource;
 
-impl RoutedResource for DataTypeResource {
+impl DataTypeResource {
     /// Create routes for interacting with data types.
-    fn routes<S, A>() -> Router
+    pub(crate) fn routes<S, A>() -> Router
     where
         S: StorePool + Send + Sync + 'static,
         A: AuthorizationApiPool + Send + Sync + 'static,
diff --git a/libs/@local/graph/api/src/rest/entity.rs b/libs/@local/graph/api/src/rest/entity.rs
index 3beb22affb7..872ec71dc37 100644
--- a/libs/@local/graph/api/src/rest/entity.rs
+++ b/libs/@local/graph/api/src/rest/entity.rs
@@ -90,9 +90,8 @@ use type_system::{
 use utoipa::{OpenApi, ToSchema};
 
 use crate::rest::{
-    AuthenticatedUserHeader, OpenApiQuery, PermissionResponse, QueryLogger,
-    api_resource::RoutedResource, json::Json, status::report_to_response,
-    utoipa_typedef::subgraph::Subgraph,
+    AuthenticatedUserHeader, OpenApiQuery, PermissionResponse, QueryLogger, json::Json,
+    status::report_to_response, utoipa_typedef::subgraph::Subgraph,
 };
 
 #[derive(OpenApi)]
@@ -234,9 +233,9 @@ use crate::rest::{
 )]
 pub(crate) struct EntityResource;
 
-impl RoutedResource for EntityResource {
+impl EntityResource {
     /// Create routes for interacting with entities.
-    fn routes<S, A>() -> Router
+    pub(crate) fn routes<S, A>() -> Router
     where
         S: StorePool + Send + Sync + 'static,
         A: AuthorizationApiPool + Send + Sync + 'static,
diff --git a/libs/@local/graph/api/src/rest/entity_type.rs b/libs/@local/graph/api/src/rest/entity_type.rs
index e5d62572d02..2d72501a439 100644
--- a/libs/@local/graph/api/src/rest/entity_type.rs
+++ b/libs/@local/graph/api/src/rest/entity_type.rs
@@ -57,7 +57,6 @@ use utoipa::{OpenApi, ToSchema};
 
 use crate::rest::{
     AuthenticatedUserHeader, OpenApiQuery, PermissionResponse, QueryLogger, RestApiStore,
-    api_resource::RoutedResource,
     json::Json,
     status::{report_to_response, status_to_response},
     utoipa_typedef::{ListOrValue, MaybeListOfEntityType, subgraph::Subgraph},
@@ -120,9 +119,9 @@ use crate::rest::{
 )]
 pub(crate) struct EntityTypeResource;
 
-impl RoutedResource for EntityTypeResource {
+impl EntityTypeResource {
     /// Create routes for interacting with entity types.
-    fn routes<S, A>() -> Router
+    pub(crate) fn routes<S, A>() -> Router
     where
         S: StorePool + Send + Sync + 'static,
         A: AuthorizationApiPool + Send + Sync + 'static,
diff --git a/libs/@local/graph/api/src/rest/mod.rs b/libs/@local/graph/api/src/rest/mod.rs
index 16d7afc07b4..a35991c207d 100644
--- a/libs/@local/graph/api/src/rest/mod.rs
+++ b/libs/@local/graph/api/src/rest/mod.rs
@@ -11,7 +11,6 @@ pub mod property_type;
 pub mod status;
 pub mod web;
 
-mod api_resource;
 mod json;
 mod utoipa_typedef;
 use alloc::{borrow::Cow, sync::Arc};
@@ -98,7 +97,6 @@ use utoipa::{
 use uuid::Uuid;
 
 use self::{
-    api_resource::RoutedResource as _,
     middleware::span_trace_layer,
     status::{report_to_response, status_to_response},
     utoipa_typedef::{
@@ -211,11 +209,11 @@ where
 
 fn api_documentation() -> Vec<openapi::OpenApi> {
     vec![
-        account::AccountResource::documentation(),
-        data_type::DataTypeResource::documentation(),
-        property_type::PropertyTypeResource::documentation(),
-        entity_type::EntityTypeResource::documentation(),
-        entity::EntityResource::documentation(),
+        account::AccountResource::openapi(),
+        data_type::DataTypeResource::openapi(),
+        property_type::PropertyTypeResource::openapi(),
+        entity_type::EntityTypeResource::openapi(),
+        entity::EntityResource::openapi(),
         web::WebResource::openapi(),
     ]
 }
diff --git a/libs/@local/graph/api/src/rest/property_type.rs b/libs/@local/graph/api/src/rest/property_type.rs
index 64618659108..5a507e57043 100644
--- a/libs/@local/graph/api/src/rest/property_type.rs
+++ b/libs/@local/graph/api/src/rest/property_type.rs
@@ -54,7 +54,6 @@ use type_system::{
 };
 use utoipa::{OpenApi, ToSchema};
 
-use super::api_resource::RoutedResource;
 use crate::rest::{
     AuthenticatedUserHeader, OpenApiQuery, PermissionResponse, QueryLogger, RestApiStore,
     json::Json,
@@ -113,9 +112,9 @@ use crate::rest::{
 )]
 pub(crate) struct PropertyTypeResource;
 
-impl RoutedResource for PropertyTypeResource {
+impl PropertyTypeResource {
     /// Create routes for interacting with property types.
-    fn routes<S, A>() -> Router
+    pub(crate) fn routes<S, A>() -> Router
     where
         S: StorePool + Send + Sync + 'static,
         A: AuthorizationApiPool + Send + Sync + 'static,
diff --git a/libs/@local/graph/authorization/src/policies/store/error.rs b/libs/@local/graph/authorization/src/policies/store/error.rs
index 371277c0795..5bbab605a3f 100644
--- a/libs/@local/graph/authorization/src/policies/store/error.rs
+++ b/libs/@local/graph/authorization/src/policies/store/error.rs
@@ -4,6 +4,18 @@ use type_system::{provenance::ActorId, web::WebId};
 
 use crate::policies::principal::{group::TeamId, role::RoleId};
 
+#[derive(Debug, derive_more::Display)]
+#[display("Could not get system account: {_variant}")]
+pub enum GetSystemAccountError {
+    #[display("Creating system account failed")]
+    CreateSystemAccountFailed,
+
+    #[display("Store operation failed")]
+    StoreError,
+}
+
+impl Error for GetSystemAccountError {}
+
 #[derive(Debug, derive_more::Display)]
 #[display("Could not create actor: {_variant}")]
 pub enum ActorCreationError {
diff --git a/libs/@local/graph/authorization/src/policies/store/mod.rs b/libs/@local/graph/authorization/src/policies/store/mod.rs
index 0a782e4cec2..502afff35c2 100644
--- a/libs/@local/graph/authorization/src/policies/store/mod.rs
+++ b/libs/@local/graph/authorization/src/policies/store/mod.rs
@@ -8,16 +8,17 @@ use std::collections::{
 
 use error_stack::{Report, bail, ensure};
 use type_system::{
-    knowledge::entity::id::EntityUuid,
+    knowledge::{entity::id::EntityUuid, property::PropertyObjectWithMetadata},
+    ontology::VersionedUrl,
     provenance::{ActorEntityUuid, ActorId, ActorType, MachineId, UserId},
     web::WebId,
 };
 use uuid::Uuid;
 
 use self::error::{
-    ActorCreationError, ContextCreationError, GetPoliciesError, PolicyStoreError,
-    RoleAssignmentError, TeamCreationError, TeamRoleCreationError, WebCreationError,
-    WebRoleCreationError,
+    ActorCreationError, ContextCreationError, GetPoliciesError, GetSystemAccountError,
+    PolicyStoreError, RoleAssignmentError, TeamCreationError, TeamRoleCreationError,
+    WebCreationError, WebRoleCreationError,
 };
 use super::{
     ContextBuilder, Policy, PolicyId,
@@ -56,6 +57,12 @@ pub enum RoleCreationParameter {
     },
 }
 
+#[derive(Debug, serde::Serialize, serde::Deserialize)]
+pub struct CreateUserParameter {
+    pub entity_type_id: VersionedUrl,
+    pub properties: PropertyObjectWithMetadata,
+}
+
 #[derive(Debug, serde::Serialize, serde::Deserialize)]
 pub struct CreateWebParameter {
     pub id: Option<Uuid>,
@@ -63,6 +70,21 @@ pub struct CreateWebParameter {
 
 #[trait_variant::make(PrincipalStore: Send)]
 pub trait LocalPrincipalStore {
+    /// Searches for the system account and returns its ID.
+    ///
+    /// If the system account does not exist, it is created. Calling this function
+    /// also implies basic permissions on the system account, so it can be used to
+    /// further configure the system.
+    ///
+    /// # Errors
+    ///
+    /// - [`StoreError`] if the underlying store returns an error
+    ///
+    /// [`StoreError`]: GetSystemAccountError::StoreError
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>>;
+
     /// Creates a new web and returns its ID.
     ///
     /// # Errors
diff --git a/libs/@local/graph/postgres-store/src/store/postgres/mod.rs b/libs/@local/graph/postgres-store/src/store/postgres/mod.rs
index 768c2be68a6..e5e69a8ec5b 100644
--- a/libs/@local/graph/postgres-store/src/store/postgres/mod.rs
+++ b/libs/@local/graph/postgres-store/src/store/postgres/mod.rs
@@ -11,13 +11,17 @@ use core::{fmt::Debug, hash::Hash};
 use std::collections::HashMap;
 
 use error_stack::{Report, ResultExt as _};
+use futures::TryStreamExt as _;
 use hash_graph_authorization::{
     AuthorizationApi,
     backend::ModifyRelationshipOperation,
     policies::{
         Authorized, PartialResourceId, PolicySet, Request, RequestContext,
         action::ActionName,
-        store::{CreateWebParameter, PrincipalStore, error::WebCreationError},
+        store::{
+            CreateWebParameter, PrincipalStore,
+            error::{GetSystemAccountError, WebCreationError},
+        },
     },
     schema::{
         AccountGroupAdministratorSubject, AccountGroupRelationAndSubject, WebDataTypeViewerSubject,
@@ -34,10 +38,10 @@ use hash_graph_store::{
     error::{InsertionError, QueryError, UpdateError},
     query::ConflictBehavior,
 };
-use hash_graph_temporal_versioning::{LeftClosedTemporalInterval, TransactionTime};
+use hash_graph_temporal_versioning::{LeftClosedTemporalInterval, Timestamp, TransactionTime};
 use hash_status::StatusCode;
 use hash_temporal_client::TemporalClient;
-use postgres_types::Json;
+use postgres_types::{Json, ToSql};
 use time::OffsetDateTime;
 use tokio_postgres::{GenericClient as _, error::SqlState};
 use type_system::{
@@ -57,7 +61,7 @@ use type_system::{
         property_type::{PropertyType, schema::PropertyTypeReference},
         provenance::{OntologyEditionProvenance, OntologyOwnership, OntologyProvenance},
     },
-    provenance::{ActorEntityUuid, ActorId},
+    provenance::{ActorEntityUuid, ActorId, ActorType, MachineId},
     web::{ActorGroupId, WebId},
 };
 use uuid::Uuid;
@@ -92,11 +96,112 @@ pub struct PostgresStore<C, A> {
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account because it is the only machine
+        // which could create new machines.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // TODO: Create an actual machine once we have proper machine management in the
+            // database.
+            // self.create_machine(None)
+            //     .await
+            //     .change_context(GetSystemAccountError::CreateSystemAccountFailed)?
+
+            // Use the public NIL UUID as actor here as we don't have a valid one, yet.
+            let machine_id = ActorEntityUuid::new(EntityUuid::new(Uuid::new_v4()));
+            self.insert_account_id(
+                ActorEntityUuid::new(EntityUuid::new(Uuid::nil())),
+                InsertAccountIdParams {
+                    account_type: ActorType::Machine,
+                    account_id: machine_id,
+                },
+            )
+            .await
+            .change_context(GetSystemAccountError::CreateSystemAccountFailed)?;
+
+            tracing::info!(
+                system_account = %machine_id,
+                "Created new system account"
+            );
+
+            MachineId::new(machine_id)
+        };
+
+        Ok(machine_id)
+    }
+
     async fn create_web(
         &mut self,
         actor: ActorId,
diff --git a/libs/@local/graph/type-fetcher/src/store.rs b/libs/@local/graph/type-fetcher/src/store.rs
index 94abf1d7fc7..17a77c9f3bf 100644
--- a/libs/@local/graph/type-fetcher/src/store.rs
+++ b/libs/@local/graph/type-fetcher/src/store.rs
@@ -5,7 +5,10 @@ use std::collections::{HashMap, HashSet};
 use error_stack::{Report, ResultExt as _};
 use hash_graph_authorization::{
     AuthorizationApi,
-    policies::store::{CreateWebParameter, PrincipalStore, error::WebCreationError},
+    policies::store::{
+        CreateWebParameter, PrincipalStore,
+        error::{GetSystemAccountError, WebCreationError},
+    },
     schema::{
         DataTypeRelationAndSubject, DataTypeViewerSubject, EntityRelationAndSubject,
         EntityTypeInstantiatorSubject, EntityTypeRelationAndSubject, EntityTypeViewerSubject,
@@ -72,7 +75,7 @@ use type_system::{
         property_type::{PropertyType, PropertyTypeMetadata},
         provenance::{OntologyOwnership, ProvidedOntologyEditionProvenance},
     },
-    provenance::{ActorEntityUuid, ActorId, ActorType, OriginProvenance, OriginType},
+    provenance::{ActorEntityUuid, ActorId, ActorType, MachineId, OriginProvenance, OriginType},
     web::WebId,
 };
 
@@ -169,6 +172,12 @@ where
     S: PrincipalStore + Send,
     A: Send,
 {
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        self.store.get_or_create_system_account().await
+    }
+
     async fn create_web(
         &mut self,
         actor: ActorId,


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 13 out of 14 changed files in this pull request and generated 1 comment.

<details>
<summary>Files not reviewed (1)</summary>

* **libs/@local/graph/api/openapi/openapi.json**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**apps/hash-api/src/graph/knowledge/system-types/user.ts:289**
* The removal of the conditional logic that checks for an existing userAccountId in createUser may introduce regressions if a userAccountId is supplied. Confirm the intended behavior or reinstate the check if external callers rely on providing an existing userAccountId.
```
    // Removed conditional assignment of userAccountId
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:13:59Z
user: login: hashdotai
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T10:15:37Z
user: login: hashdotai
body: This PR moves the system account creation logic from the API layer to the Graph, which is a good architectural change. The implementation looks generally sound, and the approach of first searching for an existing system account before creating a new one is sensible.

I do have a few concerns about clarity and maintainability:

1. There's commented-out code that should be removed
2. Some of the logic, particularly around using Uuid::nil() and the fallback detection of a system account could benefit from additional comments
3. There are opportunities to improve type safety in the API response

However, these are minor issues that can be addressed with small changes, and the overall approach of moving this functionality to the Graph layer is sound. The implementation correctly abstracts the system account creation logic away from the API, making it reusable across different parts of the application.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T10:16:00Z
user: login: TimDiekmann
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T10:16:25Z
user: login: TimDiekmann
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T10:18:08Z
user: login: hashdotai
body: This PR successfully moves the system account creation logic to the Graph component, making it accessible through a dedicated API endpoint. This is a good architectural improvement that will support bootstrapping permissions as mentioned in the PR description.

I appreciate the clean implementation of the `get_or_create_system_account` functionality and the addition of the REST endpoint. The code is generally well-structured and follows the existing patterns in the codebase.

A few observations:
1. The removal of the `RoutedResource` trait seems to be part of this refactoring, which simplifies the architecture by moving to direct implementations.
2. The system account discovery logic has two approaches (looking for the first ontology type creator or checking if there's only one machine account), which is a reasonable fallback strategy.
3. The OpenAPI documentation for the new endpoint is well-defined.

My comments are primarily focused on improving documentation for future maintainability, particularly around some of the design decisions made in the implementation. These aren't blocking issues, but addressing them would improve the code quality.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T10:18:55Z
user: login: TimDiekmann
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T10:21:33Z
user: login: TimDiekmann
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T10:21:46Z
user: login: CiaranMn
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-15T10:03:28Z

Review comments:
diff_hunk: @@ -92,11 +96,108 @@ pub struct PostgresStore<C, A> {
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // self.create_machine(None)
+            //     .await
+            //     .change_context(GetSystemAccountError::CreateSystemAccountFailed)?
+
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: Copilot
body: [nitpick] Consider removing the commented-out code in the system account creation block if it is no longer needed, to improve code clarity and reduce potential confusion.
```suggestion

```
created_at: 2025-04-08T10:13:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -92,11 +96,108 @@ pub struct PostgresStore<C, A> {
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // self.create_machine(None)
+            //     .await
+            //     .change_context(GetSystemAccountError::CreateSystemAccountFailed)?
+
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: hashdotai
body: @copilot-pull-request-reviewer Good point! I agree that we should remove those commented-out lines. They're likely remnants from the refactoring process and don't add any value to the code. Keeping them might cause confusion about which implementation is correct or make someone wonder if the commented code should be restored in the future. Clean code should be free of such distractions.
created_at: 2025-04-08T10:15:37Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@ pub struct PostgresStore<C, A> {
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // self.create_machine(None)
+            //     .await
+            //     .change_context(GetSystemAccountError::CreateSystemAccountFailed)?
+
+            let machine_id = ActorEntityUuid::new(EntityUuid::new(Uuid::new_v4()));
+            self.insert_account_id(
+                ActorEntityUuid::new(EntityUuid::new(Uuid::nil())),
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: hashdotai
body: Instead of using Uuid::nil() as the actor ID for creating the system account, consider using a more explicit approach or providing a comment to explain why Uuid::nil() is used. This helps with future maintainability and understanding the code's intent.
created_at: 2025-04-08T10:15:59Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: hashdotai
body: ```suggestion
            .then(|row| {
```

Using `map` here might lead to confusion as you're transforming the row rather than mapping over a collection. Consider using `then` which better communicates the intent of this operation in an async context.
created_at: 2025-04-08T10:15:59Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: hashdotai
body: It would be helpful to add a comment explaining why we need to check for a single machine account as an alternative method for identifying the system account. This adds context to the implementation.
created_at: 2025-04-08T10:15:59Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // self.create_machine(None)
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: hashdotai
body: These commented-out lines should be removed as they don't add value and might cause confusion.
created_at: 2025-04-08T10:15:59Z
author_association: MEMBER
diff_hunk: @@ -103,6 +106,47 @@ impl RoutedResource for AccountResource {
     }
 }
 
+#[utoipa::path(
+    get,
+    path = "/system_account",
+    tag = "Account",
+    responses(
+        (status = 200, content_type = "application/json", description = "The schema of the created account", body = MachineId),
+
+        (status = 500, description = "Store error occurred"),
+    )
+)]
+#[tracing::instrument(
+    level = "info",
+    skip(store_pool, authorization_api_pool, temporal_client)
+)]
+async fn get_or_create_system_account<S, A>(
+    authorization_api_pool: Extension<Arc<A>>,
+    temporal_client: Extension<Option<Arc<TemporalClient>>>,
+    store_pool: Extension<Arc<S>>,
+) -> Result<Json<MachineId>, Response>
path: libs/@local/graph/api/src/rest/account.rs
user: login: hashdotai
body: Add stronger typing for the return value in the REST API. Even though you're using the OpenAPI schema to document the response type, it would be better to have the actual Rust code reflect this with a more specific type than just `Json<MachineId>`.
created_at: 2025-04-08T10:16:00Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@ pub struct PostgresStore<C, A> {
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // self.create_machine(None)
+            //     .await
+            //     .change_context(GetSystemAccountError::CreateSystemAccountFailed)?
+
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: TimDiekmann
body: You're both wrong. It was intentionally added so we can use it when the `create_machine` function can be used.
created_at: 2025-04-08T10:16:25Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: TimDiekmann
body: Oh, great, this does not even exist...
created_at: 2025-04-08T10:18:08Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@ pub struct PostgresStore<C, A> {
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: hashdotai
body: Consider adding a more detailed comment here explaining why checking for a single machine account is a valid fallback mechanism for identifying the system account. This would help future developers understand this logic and the assumptions behind it.
created_at: 2025-04-08T10:18:55Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // self.create_machine(None)
+            //     .await
+            //     .change_context(GetSystemAccountError::CreateSystemAccountFailed)?
+
+            let machine_id = ActorEntityUuid::new(EntityUuid::new(Uuid::new_v4()));
+            self.insert_account_id(
+                ActorEntityUuid::new(EntityUuid::new(Uuid::nil())),
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: hashdotai
body: The use of `Uuid::nil()` as the actor ID for creating the system account should be explained with a comment. Is this because we need a placeholder actor but don't have a valid one yet? Adding context will help future maintainers understand this design decision.
created_at: 2025-04-08T10:18:55Z
author_association: MEMBER
diff_hunk: @@ -103,6 +106,47 @@ impl RoutedResource for AccountResource {
     }
 }
 
+#[utoipa::path(
+    get,
+    path = "/system_account",
+    tag = "Account",
+    responses(
+        (status = 200, content_type = "application/json", description = "The schema of the created account", body = MachineId),
+
+        (status = 500, description = "Store error occurred"),
+    )
+)]
+#[tracing::instrument(
+    level = "info",
+    skip(store_pool, authorization_api_pool, temporal_client)
+)]
+async fn get_or_create_system_account<S, A>(
+    authorization_api_pool: Extension<Arc<A>>,
+    temporal_client: Extension<Option<Arc<TemporalClient>>>,
+    store_pool: Extension<Arc<S>>,
+) -> Result<Json<MachineId>, Response>
path: libs/@local/graph/api/src/rest/account.rs
user: login: hashdotai
body: Consider defining a typed response struct for this endpoint rather than using `Json<MachineId>` directly. This would make the API contract more explicit and provide better documentation through the type system.
created_at: 2025-04-08T10:18:55Z
author_association: MEMBER
diff_hunk: @@ -98,7 +97,6 @@ use utoipa::{
 use uuid::Uuid;
 
 use self::{
-    api_resource::RoutedResource as _,
path: libs/@local/graph/api/src/rest/mod.rs
user: login: hashdotai
body: Since you're removing the `RoutedResource` trait, ensure you've updated all documentation references to it elsewhere in the codebase. The pattern change from trait-based to impl-based routing should be consistently applied.
created_at: 2025-04-08T10:18:55Z
author_association: MEMBER
diff_hunk: @@ -103,6 +106,47 @@ impl RoutedResource for AccountResource {
     }
 }
 
+#[utoipa::path(
+    get,
+    path = "/system_account",
+    tag = "Account",
+    responses(
+        (status = 200, content_type = "application/json", description = "The schema of the created account", body = MachineId),
+
+        (status = 500, description = "Store error occurred"),
+    )
+)]
+#[tracing::instrument(
+    level = "info",
+    skip(store_pool, authorization_api_pool, temporal_client)
+)]
+async fn get_or_create_system_account<S, A>(
+    authorization_api_pool: Extension<Arc<A>>,
+    temporal_client: Extension<Option<Arc<TemporalClient>>>,
+    store_pool: Extension<Arc<S>>,
+) -> Result<Json<MachineId>, Response>
path: libs/@local/graph/api/src/rest/account.rs
user: login: TimDiekmann
body: This _is_ strongly typed: `MachineId`
created_at: 2025-04-08T10:21:33Z
author_association: MEMBER
diff_hunk: @@ -92,11 +96,108 @@
     pub settings: PostgresStoreSettings,
 }
 
-impl<C, A> PrincipalStore for PostgresStore<C, A>
+impl<C, A> PostgresStore<C, A>
 where
     C: AsClient,
     A: Send + Sync,
 {
+    async fn search_existing_system_account(
+        &self,
+    ) -> Result<Option<MachineId>, Report<GetSystemAccountError>> {
+        let machine = self
+            .as_client()
+            .query_opt(
+                "
+                SELECT base_url, version, lower(transaction_time), CAST(provenance->>'createdById' AS UUID)
+                FROM ontology_temporal_metadata
+                JOIN ontology_ids ON ontology_temporal_metadata.ontology_id = ontology_ids.ontology_id
+                ORDER BY transaction_time ASC
+                LIMIT 1;
+                ",
+                &[],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map(|row| {
+                let ontology_type_id = VersionedUrl {
+                    base_url: row.get(0),
+                    version: row.get(1),
+                };
+                let transaction_time: Timestamp<TransactionTime> = row.get(2);
+                let system_account = MachineId::new(row.get(3));
+                tracing::info!(
+                    %system_account,
+                    "Found system account using ontology type `{ontology_type_id}` created at {transaction_time}"
+                );
+                system_account
+            });
+        if let Some(machine) = machine {
+            return Ok(Some(machine));
+        }
+
+        // We have not identified the machine, yet. We can check if there are more than one machine.
+        // If there is only one machine, it is the system account.
+        let machines = self
+            .as_client()
+            .query_raw(
+                "SELECT account_id FROM accounts LIMIT 2",
+                [] as [&(dyn ToSql + Sync); 0],
+            )
+            .await
+            .change_context(GetSystemAccountError::StoreError)?
+            .map_ok(|row| MachineId::new(row.get(0)))
+            .try_collect::<Vec<_>>()
+            .await
+            .change_context(GetSystemAccountError::StoreError)?;
+
+        if machines.len() == 1 {
+            tracing::info!(
+                system_account = %machines[0],
+                "Found system account due to only one machine"
+            );
+            return Ok(Some(machines[0]));
+        }
+
+        Ok(None)
+    }
+}
+impl<C, A> PrincipalStore for PostgresStore<C, A>
+where
+    C: AsClient,
+    A: AuthorizationApi + Send + Sync,
+{
+    async fn get_or_create_system_account(
+        &mut self,
+    ) -> Result<MachineId, Report<GetSystemAccountError>> {
+        let machine_id = if let Some(machine) = self.search_existing_system_account().await? {
+            machine
+        } else {
+            // self.create_machine(None)
path: libs/@local/graph/postgres-store/src/store/postgres/mod.rs
user: login: TimDiekmann
body: See above
created_at: 2025-04-08T10:21:46Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-04-08T10:18:51Z
body: ## [Codecov](https://app.codecov.io/gh/hashintel/hash/pull/6884?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) Report
Attention: Patch coverage is `0%` with `6 lines` in your changes missing coverage. Please review.
> Project coverage is 30.73%. Comparing base [(`401ece7`)](https://app.codecov.io/gh/hashintel/hash/commit/401ece72be4752221c58c482bfc878126a477513?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) to head [(`7aa5fb4`)](https://app.codecov.io/gh/hashintel/hash/commit/7aa5fb4e222ffc5f9213b92d9b3f826533f11630?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel).
> Report is 28 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/hashintel/hash/pull/6884?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | Patch % | Lines |
|---|---|---|
| [.../hash-api/src/graph/knowledge/system-types/user.ts](https://app.codecov.io/gh/hashintel/hash/pull/6884?src=pr&el=tree&filepath=apps%2Fhash-api%2Fsrc%2Fgraph%2Fknowledge%2Fsystem-types%2Fuser.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel#diff-YXBwcy9oYXNoLWFwaS9zcmMvZ3JhcGgva25vd2xlZGdlL3N5c3RlbS10eXBlcy91c2VyLnRz) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/hashintel/hash/pull/6884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) |
| [apps/hash-api/src/graph/system-account.ts](https://app.codecov.io/gh/hashintel/hash/pull/6884?src=pr&el=tree&filepath=apps%2Fhash-api%2Fsrc%2Fgraph%2Fsystem-account.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel#diff-YXBwcy9oYXNoLWFwaS9zcmMvZ3JhcGgvc3lzdGVtLWFjY291bnQudHM=) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/hashintel/hash/pull/6884?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main    #6884   +/-   ##
=======================================
  Coverage   30.72%   30.73%           
=======================================
  Files         606      606           
  Lines       23280    23266   -14     
  Branches     2966     2965    -1     
=======================================
- Hits         7153     7151    -2     
+ Misses      16070    16058   -12     
  Partials       57       57           
```

| [Flag](https://app.codecov.io/gh/hashintel/hash/pull/6884/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | Coverage Δ | |
|---|---|---|
| [apps.hash-ai-worker-ts](https://app.codecov.io/gh/hashintel/hash/pull/6884/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | `1.32% <ø> (+<0.01%)` | :arrow_up: |
| [apps.hash-api](https://app.codecov.io/gh/hashintel/hash/pull/6884/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | `0.00% <0.00%> (ø)` | |
| [local.hash-backend-utils](https://app.codecov.io/gh/hashintel/hash/pull/6884/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | `3.94% <ø> (ø)` | |
| [local.hash-graph-sdk](https://app.codecov.io/gh/hashintel/hash/pull/6884/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | `0.00% <ø> (ø)` | |
| [local.hash-isomorphic-utils](https://app.codecov.io/gh/hashintel/hash/pull/6884/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | `0.00% <ø> (ø)` | |
| [local.hash-subgraph](https://app.codecov.io/gh/hashintel/hash/pull/6884/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel) | `21.19% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/hashintel/hash/pull/6884?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=hashintel).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-09T19:56:41Z
body: <!-- add-pr-comment:benchmark-results -->

## Benchmark results
<details><summary><h2>@rust/hash-graph-benches – Integrations</h2></summary>

## representative_read_entity

| Function | Value | Mean | Flame graphs | 
|----------|-------|------|--------------|
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/block/v/1` | $$17.3 \mathrm{ms} \pm 199 \mathrm{μs}\left({\color{red}6.72 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_5/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/book/v/1` | $$17.2 \mathrm{ms} \pm 180 \mathrm{μs}\left({\color{gray}-1.167 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_8/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/page/v/2` | $$16.6 \mathrm{ms} \pm 197 \mathrm{μs}\left({\color{lightgreen}-30.231 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/organization/v/1` | $$17.1 \mathrm{ms} \pm 174 \mathrm{μs}\left({\color{gray}-0.136 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_6/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/person/v/1` | $$16.6 \mathrm{ms} \pm 224 \mathrm{μs}\left({\color{lightgreen}-25.989 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_3/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/playlist/v/1` | $$15.9 \mathrm{ms} \pm 192 \mathrm{μs}\left({\color{lightgreen}-33.314 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_2/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/song/v/1` | $$16.1 \mathrm{ms} \pm 181 \mathrm{μs}\left({\color{gray}-4.269 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_9/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1` | $$16.9 \mathrm{ms} \pm 268 \mathrm{μs}\left({\color{gray}2.56 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_7/flamegraph.svg) |
| entity_by_id | entity type ID: `https://blockprotocol.org/@alice/types/entity-type/building/v/1` | $$17.4 \mathrm{ms} \pm 185 \mathrm{μs}\left({\color{red}6.71 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity/entity_by_id/entity+type+ID_+`https___blockprotocol.org_@alice_types_entity-t_4/flamegraph.svg) |


## scaling_read_entity_complete_zero_depth

| Function | Value | Mean | Flame graphs | 
|----------|-------|------|--------------|
| entity_by_id | 10 entities | $$2.17 \mathrm{ms} \pm 16.7 \mathrm{μs}\left({\color{gray}-3.637 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_zero_depth/entity_by_id/10+entities/flamegraph.svg) |
| entity_by_id | 5 entities | $$2.04 \mathrm{ms} \pm 12.5 \mathrm{μs}\left({\color{gray}-0.249 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_zero_depth/entity_by_id/5+entities/flamegraph.svg) |
| entity_by_id | 50 entities | $$4.35 \mathrm{ms} \pm 21.3 \mathrm{μs}\left({\color{gray}4.66 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_zero_depth/entity_by_id/50+entities/flamegraph.svg) |
| entity_by_id | 25 entities | $$3.45 \mathrm{ms} \pm 19.3 \mathrm{μs}\left({\color{red}22.4 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_zero_depth/entity_by_id/25+entities/flamegraph.svg) |
| entity_by_id | 1 entities | $$2.02 \mathrm{ms} \pm 6.51 \mathrm{μs}\left({\color{gray}-0.111 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_zero_depth/entity_by_id/1+entities/flamegraph.svg) |


## representative_read_entity_type

| Function | Value | Mean | Flame graphs | 
|----------|-------|------|--------------|
| get_entity_type_by_id | Account ID: `d4e16033-c281-4cde-aa35-9085bf2e7579` | $$2.17 \mathrm{ms} \pm 7.76 \mathrm{μs}\left({\color{gray}-0.087 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_entity_type/get_entity_type_by_id/Account+ID_+`d4e16033-c281-4cde-aa35-9085bf2e7579`/flamegraph.svg) |


## scaling_read_entity_linkless

| Function | Value | Mean | Flame graphs | 
|----------|-------|------|--------------|
| entity_by_id | 10000 entities | $$13.9 \mathrm{ms} \pm 63.4 \mathrm{μs}\left({\color{red}42.5 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_linkless/entity_by_id/10000+entities/flamegraph.svg) |
| entity_by_id | 100 entities | $$2.26 \mathrm{ms} \pm 9.27 \mathrm{μs}\left({\color{gray}-0.313 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_linkless/entity_by_id/100+entities/flamegraph.svg) |
| entity_by_id | 10 entities | $$2.05 \mathrm{ms} \pm 6.72 \mathrm{μs}\left({\color{gray}-0.199 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_linkless/entity_by_id/10+entities/flamegraph.svg) |
| entity_by_id | 1000 entities | $$3.04 \mathrm{ms} \pm 19.8 \mathrm{μs}\left({\color{lightgreen}-5.432 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_linkless/entity_by_id/1000+entities/flamegraph.svg) |
| entity_by_id | 1 entities | $$2.03 \mathrm{ms} \pm 6.99 \mathrm{μs}\left({\color{gray}-0.248 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_linkless/entity_by_id/1+entities/flamegraph.svg) |


## representative_read_multiple_entities

| Function | Value | Mean | Flame graphs | 
|----------|-------|------|--------------|
| entity_by_property | depths: DT=0, PT=2, ET=2, E=2 | $$54.8 \mathrm{ms} \pm 368 \mathrm{μs}\left({\color{gray}0.041 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/entity_by_property/depths_+DT=0,+PT=2,+ET=2,+E=2/flamegraph.svg) |
| entity_by_property | depths: DT=0, PT=0, ET=0, E=0 | $$39.7 \mathrm{ms} \pm 209 \mathrm{μs}\left({\color{gray}0.760 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/entity_by_property/depths_+DT=0,+PT=0,+ET=0,+E=0/flamegraph.svg) |
| entity_by_property | depths: DT=0, PT=0, ET=0, E=2 | $$44.6 \mathrm{ms} \pm 433 \mathrm{μs}\left({\color{gray}0.169 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/entity_by_property/depths_+DT=0,+PT=0,+ET=0,+E=2/flamegraph.svg) |
| entity_by_property | depths: DT=2, PT=2, ET=2, E=2 | $$58.4 \mathrm{ms} \pm 372 \mathrm{μs}\left({\color{gray}1.16 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/entity_by_property/depths_+DT=2,+PT=2,+ET=2,+E=2/flamegraph.svg) |
| entity_by_property | depths: DT=255, PT=255, ET=255, E=255 | $$67.0 \mathrm{ms} \pm 353 \mathrm{μs}\left({\color{gray}-1.734 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/entity_by_property/depths_+DT=255,+PT=255,+ET=255,+E=255/flamegraph.svg) |
| entity_by_property | depths: DT=0, PT=0, ET=2, E=2 | $$49.9 \mathrm{ms} \pm 374 \mathrm{μs}\left({\color{gray}-1.073 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/entity_by_property/depths_+DT=0,+PT=0,+ET=2,+E=2/flamegraph.svg) |
| link_by_source_by_property | depths: DT=0, PT=2, ET=2, E=2 | $$89.5 \mathrm{ms} \pm 704 \mathrm{μs}\left({\color{gray}-1.579 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/link_by_source_by_property/depths_+DT=0,+PT=2,+ET=2,+E=2/flamegraph.svg) |
| link_by_source_by_property | depths: DT=0, PT=0, ET=0, E=0 | $$40.2 \mathrm{ms} \pm 455 \mathrm{μs}\left({\color{gray}-1.010 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/link_by_source_by_property/depths_+DT=0,+PT=0,+ET=0,+E=0/flamegraph.svg) |
| link_by_source_by_property | depths: DT=0, PT=0, ET=0, E=2 | $$76.3 \mathrm{ms} \pm 844 \mathrm{μs}\left({\color{gray}-2.068 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/link_by_source_by_property/depths_+DT=0,+PT=0,+ET=0,+E=2/flamegraph.svg) |
| link_by_source_by_property | depths: DT=2, PT=2, ET=2, E=2 | $$93.1 \mathrm{ms} \pm 686 \mathrm{μs}\left({\color{gray}-2.407 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/link_by_source_by_property/depths_+DT=2,+PT=2,+ET=2,+E=2/flamegraph.svg) |
| link_by_source_by_property | depths: DT=255, PT=255, ET=255, E=255 | $$102 \mathrm{ms} \pm 725 \mathrm{μs}\left({\color{gray}-0.595 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/link_by_source_by_property/depths_+DT=255,+PT=255,+ET=255,+E=255/flamegraph.svg) |
| link_by_source_by_property | depths: DT=0, PT=0, ET=2, E=2 | $$85.2 \mathrm{ms} \pm 594 \mathrm{μs}\left({\color{gray}0.427 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/representative_read_multiple_entities/link_by_source_by_property/depths_+DT=0,+PT=0,+ET=2,+E=2/flamegraph.svg) |


## scaling_read_entity_complete_one_depth

| Function | Value | Mean | Flame graphs | 
|----------|-------|------|--------------|
| entity_by_id | 10 entities | $$58.1 \mathrm{ms} \pm 121 \mathrm{μs}\left({\color{red}76.8 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_one_depth/entity_by_id/10+entities/flamegraph.svg) |
| entity_by_id | 5 entities | $$27.3 \mathrm{ms} \pm 145 \mathrm{μs}\left({\color{gray}0.161 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_one_depth/entity_by_id/5+entities/flamegraph.svg) |
| entity_by_id | 50 entities | $$5.36 \mathrm{s} \pm 535 \mathrm{ms}\left({\color{gray}-0.359 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_one_depth/entity_by_id/50+entities/flamegraph.svg) |
| entity_by_id | 25 entities | $$85.5 \mathrm{ms} \pm 358 \mathrm{μs}\left({\color{lightgreen}-52.657 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_one_depth/entity_by_id/25+entities/flamegraph.svg) |
| entity_by_id | 1 entities | $$21.4 \mathrm{ms} \pm 114 \mathrm{μs}\left({\color{gray}2.06 \mathrm{\\%}}\right) $$ | [Flame Graph](https://benchmarks.hash.dev/7aa5fb4e222ffc5f9213b92d9b3f826533f11630/scaling_read_entity_complete_one_depth/entity_by_id/1+entities/flamegraph.svg) |

</details>

author_association: CONTRIBUTOR