BE-426: Merge graph test-server into admin endpoint in main binary#8497
Conversation
Replace the standalone `hash-graph-test-server` crate with an admin API served from the main `hash-graph` binary. The admin server can run as a dedicated `admin-server` subcommand or be embedded into the main `server` subcommand via `--embed-admin`. - Add `admin.rs` REST routes in `hash-graph-api` (snapshot restore, delete accounts/types/entities) - Add `admin-server` subcommand with standalone healthcheck support - Add `--embed-admin` and `--embed-type-fetcher` flags to `server` - Refactor type-fetcher into the same standalone/embeddable pattern - Remove `hash-graph-test-server` crate, feature flag, and Docker build arg - Replace `DELETE /entities` with `POST /entities/delete` using the new `DeleteEntitiesParams` API - Remove `delete_all_entities` from postgres store - Fix `HttpTracingLayer` ordering so it actually wraps admin routes - Move `install_error_stack_hook` into `run_admin_server` so both standalone and embedded paths get it - Update all TS integration tests to use `admin-server` imports - Update `.env` defaults for embedded dev workflow
…tdown Introduces a shared lifecycle coordinator with shutdown/abort CancellationTokens and a ShutdownGuard that detects unexpected task exits (including panics). All server subcommands now use select! over ctrl-c and abort, return non-zero exit codes on crash, and support double ctrl-c for force shutdown. Also fixes: bind-before-log ordering in REST/admin servers, layer ordering in admin routes, deserialization errors returning 500 instead of 422, and deduplicates TemporalClient Arc wrapping.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Refactors the Updates Docker/CI/env config to drop Written by Cursor Bugbot for commit baf6251. This will update automatically on new commits. Configure here. |
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## t/be-311-implement-deletion-for-entities #8497 +/- ##
============================================================================
- Coverage 62.80% 62.75% -0.06%
============================================================================
Files 1303 1304 +1
Lines 133446 133554 +108
Branches 5509 5509
============================================================================
- Hits 83811 83809 -2
- Misses 48721 48831 +110
Partials 914 914
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:
|
🤖 Augment PR SummarySummary: This PR removes the standalone Changes:
Technical Notes: Entry points now use 🤖 Was this summary useful? React with 👍 or 👎 |
- Add error_for_status() to healthcheck endpoints (admin + server) - Remove unnecessary shutdown.clone() in ServerLifecycle::spawn - Remove dead FEATURES array from Dockerfile - Remove @rust/hash-graph-type-fetcher from root start script - Add @apps/hash-graph as benchmark dependency so turbo starts the graph server (with embedded type-fetcher) before running benchmarks - Delete turbo.json overrides that were suppressing default dependencies
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
The admin-server and type-fetcher subcommands now match the server subcommand's behavior: a second ctrl-c during graceful shutdown triggers process::exit(1) to prevent hanging on stuck tasks.
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: 1001 | 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: 1526 | 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: 51 | 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: 107 | 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: 1 | 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: 25 | 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: 26 | 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 |

🌟 What is the purpose of this PR?
Removes the standalone
hash-graph-test-servercrate and merges its functionality into the mainhash-graphbinary as an admin endpoint. Introduces a robust server lifecycle system with crash detection, graceful shutdown cascading, and non-zero exit codes.🚫 Blocked by
🔍 What does this change?
Admin endpoint migration:
libs/@local/graph/test-servercrate entirelylibs/@local/graph/api/src/rest/admin.rswith routes:/health,POST /snapshot,DELETE /accounts,DELETE /data-types,DELETE /property-types,DELETE /entity-types,POST /entities/deleteadmin-serversubcommand for standalone operation--embed-adminflag toserversubcommand for dev convenience--embed-type-fetcherflag toserversubcommand (replaces running type-fetcher separately).env, CI workflows, and Docker build to remove test-server referencesServer lifecycle overhaul (
ServerTaskTracker→ServerLifecycle):shutdown+abortCancellationTokens for coordinated shutdownShutdownGuard(Drop-based) detects unexpected task exits including panicsselect!over ctrl-c/abort in all entrypoints — crash in any component cascades shutdownprocess::exit(1))Bug fixes:
HttpTracingLayerplaced after routes (was wrapping empty router)delete_entitiesdeserialization error now returns 422 instead of 500TemporalClientArc wrapping (was created twice for REST + RPC)install_error_stack_hookmoved to run in both embedded and standalone pathsPre-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:
turbo.json's have been updated to reflect this--embed-adminand--embed-type-fetcherflags use fire-and-forget spawning without startup healthchecks. Startup failures are caught by the abort mechanism (non-zero exit) but the REST server may start before embedded services are ready. In practice this is fine since connections are on-demand.🐾 Next steps
🛡 What tests cover this?
tests/hash-backend-integrationupdated to use admin endpointtests/graph/http/tests/reset-database.httpupdated for new admin port❓ How to test this?
cargo clippy --package hash-graph --all-features— should be cleancargo run --bin hash-graph --all-features -- server --embed-admin --embed-type-fetcher— all three servers startcurl http://127.0.0.1:4001/health— admin endpoint responds