Repository navigation
BE-875: Add POST /entities/v1 - #10102
TimDiekmann wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #10102 +/- ##
==========================================
- Coverage 68.01% 68.01% -0.01%
==========================================
Files 2003 2003
Lines 205820 205798 -22
Branches 8785 8785
==========================================
- Hits 139997 139978 -19
+ Misses 64158 64155 -3
Partials 1665 1665
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will not alter performance
|
PR SummaryMedium Risk Overview Request parsing enforces non-empty Scalar is bumped to Reviewed by Cursor Bugbot for commit f3ed3d3. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
🟡 Changes recommended
Legacy errors still expose private type existence, and the new write endpoint lacks authenticated HTTP round-trip coverage.
2 open issues
What changed in this PR
Adds transactional entity creation to the public Graph API, sharing the property and metadata response shape with GET.
Changes:
- Adds
POST /entities/v1with request validation and typed errors. - Updates store error handling and post-commit workflow behavior.
- Expands schemas, documentation, and validation tests.
| File | Description |
|---|---|
| tests/hash-backend-integration/src/tests/graph/knowledge/primitive/entity.test.ts | Asserts permission error codes. |
| tests/graph/integration/postgres/multi_type.rs | Tests creation failures and rollback. |
| tests/graph/integration/postgres/lib.rs | Adapts the test store interface. |
| libs/problematic/rust/src/aide.rs | Clarifies a schema-rendering comment. |
| libs/@local/graph/type-fetcher/src/store.rs | Propagates typed creation errors. |
| libs/@local/graph/store/rust/src/error.rs | Defines entity creation errors. |
| libs/@local/graph/store/rust/src/entity/store.rs | Updates the creation contract. |
| libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs | Implements checks and revised error handling. |
| libs/@local/graph/postgres-store/src/lib.rs | Removes an unused feature flag. |
| libs/@local/graph/authorization/rust/src/policies/store/error.rs | Adds missing-type errors. |
| libs/@local/graph/authorization/rust/src/policies/components.rs | Classifies missing entity types. |
| libs/@local/graph/api/tests/snapshots/openapi/entities-v1.snap.json | Updates the API schema snapshot. |
| libs/@local/graph/api/src/rest/routing.rs | Removes the POST dead-code exemption. |
| libs/@local/graph/api/src/rest/public/entities/v1/property_metadata.rs | Converts and validates property metadata. |
| libs/@local/graph/api/src/rest/public/entities/v1/mod.rs | Registers POST alongside GET. |
| libs/@local/graph/api/src/rest/public/entities/v1/entity.rs | Returns properties and metadata. |
| libs/@local/graph/api/src/rest/public/entities/v1/create.rs | Implements the public creation endpoint. |
| libs/@local/graph/api/src/rest/documentation/scalar.rs | Serves Scalar without patching. |
| libs/@local/graph/api/src/rest/documentation/mod.rs | Updates panic documentation. |
| libs/@blockprotocol/type-system/rust/src/principal/actor_group/web.rs | Adds the web ID schema. |
| libs/@blockprotocol/type-system/rust/src/ontology/id/mod.rs | Adds the base URL schema. |
| libs/@blockprotocol/type-system/rust/src/knowledge/value/mod.rs | Adds the property value schema. |
| libs/@blockprotocol/type-system/rust/src/knowledge/property/object.rs | Adds the property object schema. |
| libs/@blockprotocol/type-system/rust/src/knowledge/property/mod.rs | Adds recursive property schemas. |
| Cargo.toml | Upgrades the Scalar dependency. |
| Cargo.lock | Records the Scalar upgrade. |
Review effort: Balanced
f3ed3d3 to
28d9812
Compare
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2002 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1002 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 3314 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 1527 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 2078 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 1033 | Flame Graph |
policy_resolution_medium
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 102 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 269 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 108 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 133 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 63 | Flame Graph |
policy_resolution_none
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 8 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 3 | Flame Graph |
policy_resolution_small
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 26 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 94 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 27 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 66 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 29 | Flame Graph |
read_scaling_complete
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id;one_depth | 1 entities | Flame Graph | |
| entity_by_id;one_depth | 10 entities | Flame Graph | |
| entity_by_id;one_depth | 25 entities | Flame Graph | |
| entity_by_id;one_depth | 5 entities | Flame Graph | |
| entity_by_id;one_depth | 50 entities | Flame Graph | |
| entity_by_id;two_depth | 1 entities | Flame Graph | |
| entity_by_id;two_depth | 10 entities | Flame Graph | |
| entity_by_id;two_depth | 25 entities | Flame Graph | |
| entity_by_id;two_depth | 5 entities | Flame Graph | |
| entity_by_id;two_depth | 50 entities | Flame Graph | |
| entity_by_id;zero_depth | 1 entities | Flame Graph | |
| entity_by_id;zero_depth | 10 entities | Flame Graph | |
| entity_by_id;zero_depth | 25 entities | Flame Graph | |
| entity_by_id;zero_depth | 5 entities | Flame Graph | |
| entity_by_id;zero_depth | 50 entities | Flame Graph |
read_scaling_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 1 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 10000 entities | Flame Graph |
representative_read_entity
| 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/building/v/1
|
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/page/v/2
|
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 |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba
|
Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | traversal_paths=0 | 0 | |
| entity_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=0 | 0 | |
| link_by_source_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true |
scenarios
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| full_test | query-limited | Flame Graph | |
| full_test | query-unlimited | Flame Graph | |
| linked_queries | query-limited | Flame Graph | |
| linked_queries | query-unlimited | Flame Graph |

🌟 Purpose
Clients of the public Graph API can read an entity, but they cannot create one. This adds
POST /entities/v1, which creates one or more entities in one transaction and returns them in the shapeGET /entities/v1/{entityId}uses.Properties follow the shape agreed for the public API:
propertiesis a plain property object, andpropertyMetadatais a separate tree of the same shape that gives the data type of every value.GETnow returns both.🔍 Changes
{ "entities": [{ "webId", "entityTypeIds", "properties", "propertyMetadata" }] }.propertyMetadatais required and has to cover every value. A request whose metadata does not fit its properties, or with an emptyentitiesorentityTypeIds, is rejected while it is parsed, with a 422 that names the path.EntityCreationErrorinstead ofInsertionError, and the endpoint maps it to problem details: 403 if the caller may not create an entity or instantiate one of its types, 422 for unknown, archived or incompatible entity types and for entities that do not match their types. An entity type the caller may not view is reported like one that does not exist. That check runs before the instantiation check, so a 403 does not reveal that a type exists.