Repository navigation
BE-877: Add GET /entities/v1/{entityId} - #10046
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #10046 +/- ##
==========================================
+ Coverage 68.02% 68.04% +0.01%
==========================================
Files 2000 2003 +3
Lines 205894 206203 +309
Branches 8782 8792 +10
==========================================
+ Hits 140053 140301 +248
- Misses 64176 64233 +57
- Partials 1665 1669 +4
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
|
There was a problem hiding this comment.
🔵 Needs a closer look
Shared authentication enforcement and transactional entity-read changes warrant final human review.
No open issues 🎉
What changed in this PR
Adds the first entity-read operation to the versioned public Graph API, returning an authorized entity’s ID and direct types.
Changes:
- Adds token-authenticated entity reads with explicit draft, archive, and unavailable-store responses.
- Routes ID lookups through permission-aware queries with protected-property masking.
- Introduces shared API state, constrained route registration, and OpenAPI validation.
| File | Description |
|---|---|
| tests/hash-backend-integration/src/tests/graph/authentication/api-token.test.ts | Tests entity access, archives, and revoked tokens. |
| tests/graph/integration/postgres/partial_updates.rs | Adapts assertions to optional entity reads. |
| tests/graph/integration/postgres/lib.rs | Updates the test store interface. |
| tests/graph/integration/postgres/entity.rs | Tests missing and hidden entity reads. |
| tests/graph/integration/postgres/email_filter_protection.rs | Tests email masking during ID lookup. |
| tests/graph/integration/postgres/drafts.rs | Updates draft existence checks. |
| libs/@local/middleware/src/problem.rs | Adds generic service-unavailable responses. |
| libs/@local/graph/type-fetcher/src/store.rs | Adapts delegated entity reads. |
| libs/@local/graph/store/rust/src/entity/store.rs | Updates read and diff contracts. |
| libs/@local/graph/postgres-store/tests/deletion/purge.rs | Checks purged entities read as absent. |
| libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs | Uses permission-aware queries for ID lookups. |
| libs/@local/graph/api/tests/snapshots/openapi/types-v1.snap.json | Removes anonymous caller documentation. |
| libs/@local/graph/api/tests/snapshots/openapi/entities-v1.snap.json | Documents the entity-read endpoint. |
| libs/@local/graph/api/src/rest/test_utils/mod.rs | Supplies state and unavailable-store fixtures. |
| libs/@local/graph/api/src/rest/test_utils/authentication.rs | Separates required and anonymous authentication tests. |
| libs/@local/graph/api/src/rest/state.rs | Adds shared store and Temporal state. |
| libs/@local/graph/api/src/rest/routing.rs | Constrains handler responses and route metadata. |
| libs/@local/graph/api/src/rest/router.rs | Injects state into versioned APIs. |
| libs/@local/graph/api/src/rest/public/types/v1.rs | Keeps only the authenticated caller route. |
| libs/@local/graph/api/src/rest/public/types/mod.rs | Supports generic API state. |
| libs/@local/graph/api/src/rest/public/mod.rs | Threads store state through public APIs. |
| libs/@local/graph/api/src/rest/public/entities/v1/mod.rs | Registers entity routes. |
| libs/@local/graph/api/src/rest/public/entities/v1/entity.rs | Implements entity reads and response contracts. |
| libs/@local/graph/api/src/rest/public/entities/mod.rs | Connects entity APIs to store state. |
| libs/@local/graph/api/src/rest/openapi/tests.rs | Tests operation IDs and schema pruning. |
| libs/@local/graph/api/src/rest/openapi/mod.rs | Validates routes and prunes unused schemas. |
| libs/@local/graph/api/src/rest/mod.rs | Adds stateful API assembly. |
| libs/@local/graph/api/src/rest/middleware/tests.rs | Tests authentication, draft rejection, and store failures. |
| libs/@local/graph/api/src/rest/middleware/mod.rs | Requires authenticated public callers. |
| libs/@local/graph/api/src/rest/legacy/entity/mod.rs | Uses mutable store access for diffs. |
| libs/@local/graph/api/src/rest/internal/mod.rs | Adopts stateful route construction. |
| libs/@local/graph/api/src/rest/internal/credentials.rs | Explicitly permits anonymous internal callers. |
| libs/@local/graph/api/src/rest/extract/tests.rs | Adopts constrained routing in tests. |
| libs/@local/graph/api/src/rest/extract/snapshots/extract-example.snap.json | Removes unused parameter schemas. |
| libs/@local/graph/api/src/rest/extract/query.rs | Narrows the test-only annotation. |
| libs/@local/graph/api/src/rest/extract/mod.rs | Removes response-body tracking. |
| libs/@local/graph/api/src/rest/extract/json.rs | Delegates response enforcement to routing. |
| libs/@local/graph/api/src/rest/credentials.rs | Restricts anonymous actor extraction. |
| libs/@local/graph/api/src/rest/caller.rs | Splits authenticated and anonymous routes. |
| libs/@local/graph/api/Cargo.toml | Adds schema-decoding and test dependencies. |
| libs/@blockprotocol/type-system/rust/src/ontology/id/mod.rs | Adds the versioned-URL JSON schema. |
| libs/@blockprotocol/type-system/rust/src/knowledge/entity/id.rs | Documents entity ID composition. |
| Cargo.toml | Declares percent-encoding dependency. |
| Cargo.lock | Records dependency updates. |
Review effort: Balanced
…api-endpoints-for-querying-entities
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
External clients need to read entities through the versioned public Graph API. This adds its first operation,
GET /entities/v1/{entityId}, which returns the ID and the entity types of an entity the caller may view, and the groundwork further operations of the public and internal APIs build on.🔍 Changes
rest::routing, which admits only handlers answering withJsonor aRejection, andopenapi::buildaccepts no other routes. It panics for two operations sharing anoperationId, and drops component schemas nothing references.State<ApiState>, which acquires it with the Temporal client.get_entity_by_idreturnsNonefor a missing or hidden entity instead of an error, so the endpoint answers404. An unreachable store answers503without naming the dependency.get_entity_by_idreads throughquery_entities, so it masks protected properties as well and reads the policies and the entity in one read-only transaction. It now takes&mut self, asquery_entitiesdoes.400for a draft's ID and404for an archived entity.401before a handler runs, andMaybeActoronly compiles for the internal API's credentials./entities/v1no longer serves the caller endpoints, and/types/v1keeps only/authenticated-caller.propertiesand the link endpoints follow once the encoding of link endpoints and direction is decided; adding fields later does not break clients.