H-4347: Move logic to create system account to Graph#6884
Conversation
There was a problem hiding this comment.
Copilot reviewed 13 out of 14 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- libs/@local/graph/api/openapi/openapi.json: Language not supported
Comments suppressed due to low confidence (1)
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
hashdotai
left a comment
There was a problem hiding this comment.
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:
- There's commented-out code that should be removed
- Some of the logic, particularly around using Uuid::nil() and the fallback detection of a system account could benefit from additional comments
- 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.
|
|
||
| let machine_id = ActorEntityUuid::new(EntityUuid::new(Uuid::new_v4())); | ||
| self.insert_account_id( | ||
| ActorEntityUuid::new(EntityUuid::new(Uuid::nil())), |
There was a problem hiding this comment.
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.
| ) | ||
| .await | ||
| .change_context(GetSystemAccountError::StoreError)? | ||
| .map(|row| { |
There was a problem hiding this comment.
| .map(|row| { | |
| .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.
There was a problem hiding this comment.
Oh, great, this does not even exist...
| return Ok(Some(machine)); | ||
| } | ||
|
|
||
| // We have not identified the machine, yet. We can check if there are more than one machine. |
There was a problem hiding this comment.
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.
| authorization_api_pool: Extension<Arc<A>>, | ||
| temporal_client: Extension<Option<Arc<TemporalClient>>>, | ||
| store_pool: Extension<Arc<S>>, | ||
| ) -> Result<Json<MachineId>, Response> |
There was a problem hiding this comment.
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>.
There was a problem hiding this comment.
This is strongly typed: MachineId
c3a0b2c to
25ba04b
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
hashdotai
left a comment
There was a problem hiding this comment.
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:
- The removal of the
RoutedResourcetrait seems to be part of this refactoring, which simplifies the architecture by moving to direct implementations. - 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.
- 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.
| return Ok(Some(machine)); | ||
| } | ||
|
|
||
| // We have not identified the machine, yet. We can check if there are more than one machine. |
There was a problem hiding this comment.
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.
|
|
||
| let machine_id = ActorEntityUuid::new(EntityUuid::new(Uuid::new_v4())); | ||
| self.insert_account_id( | ||
| ActorEntityUuid::new(EntityUuid::new(Uuid::nil())), |
There was a problem hiding this comment.
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.
| authorization_api_pool: Extension<Arc<A>>, | ||
| temporal_client: Extension<Option<Arc<TemporalClient>>>, | ||
| store_pool: Extension<Arc<S>>, | ||
| ) -> Result<Json<MachineId>, Response> |
There was a problem hiding this comment.
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.
| use uuid::Uuid; | ||
|
|
||
| use self::{ | ||
| api_resource::RoutedResource as _, |
There was a problem hiding this comment.
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.
6d0f54d to
dfa0bae
Compare
4994a42 to
7aa5fb4
Compare
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1
|
Flame Graph |
scaling_read_entity_complete_zero_depth
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 5 entities | Flame Graph | |
| entity_by_id | 50 entities | Flame Graph | |
| entity_by_id | 25 entities | Flame Graph | |
| entity_by_id | 1 entities | Flame Graph |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: d4e16033-c281-4cde-aa35-9085bf2e7579
|
Flame Graph |
scaling_read_entity_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 10000 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 1 entities | Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | depths: DT=0, PT=2, ET=2, E=2 | Flame Graph | |
| entity_by_property | depths: DT=0, PT=0, ET=0, E=0 | Flame Graph | |
| entity_by_property | depths: DT=0, PT=0, ET=0, E=2 | Flame Graph | |
| entity_by_property | depths: DT=2, PT=2, ET=2, E=2 | Flame Graph | |
| entity_by_property | depths: DT=255, PT=255, ET=255, E=255 | Flame Graph | |
| entity_by_property | depths: DT=0, PT=0, ET=2, E=2 | Flame Graph | |
| link_by_source_by_property | depths: DT=0, PT=2, ET=2, E=2 | Flame Graph | |
| link_by_source_by_property | depths: DT=0, PT=0, ET=0, E=0 | Flame Graph | |
| link_by_source_by_property | depths: DT=0, PT=0, ET=0, E=2 | Flame Graph | |
| link_by_source_by_property | depths: DT=2, PT=2, ET=2, E=2 | Flame Graph | |
| link_by_source_by_property | depths: DT=255, PT=255, ET=255, E=255 | Flame Graph | |
| link_by_source_by_property | depths: DT=0, PT=0, ET=2, E=2 | Flame Graph |
scaling_read_entity_complete_one_depth
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 5 entities | Flame Graph | |
| entity_by_id | 50 entities | Flame Graph | |
| entity_by_id | 25 entities | Flame Graph | |
| entity_by_id | 1 entities | Flame Graph |
🌟 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
Team->ActorGroup,Subteam->Team,OwnedById->WebId#6881Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR: