Skip to content

BE-761: Authenticate Cloudflare Access JWTs in the Graph API - #9235

Open
TimDiekmann wants to merge 3 commits into
t/be-765-tighten-the-authentication-provider-types-and-split-thefrom
t/be-761-authenticate-cloudflare-access-jwts-in-the-graph-api
Open

BE-761: Authenticate Cloudflare Access JWTs in the Graph API#9235
TimDiekmann wants to merge 3 commits into
t/be-765-tighten-the-authentication-provider-types-and-split-thefrom
t/be-761-authenticate-cloudflare-access-jwts-in-the-graph-api

Conversation

@TimDiekmann

@TimDiekmann TimDiekmann commented Aug 18, 2026

Copy link
Copy Markdown
Member

🌟 What is the purpose of this PR?

Adds Cloudflare Access as a third credential alongside the Kratos session and the service delegation, and replaces the admin API's own authentication scheme with the shared providers.

The two ports do not authenticate identically: the admin API deliberately runs without the session provider. Its handlers erase entities and delete users while authorizing nothing beyond "some actor", so operators reach it through Access and internal services through the shared secret — an end-user session is not a credential there.

This is what lets an operator reach the admin API through Cloudflare Access with their own identity, rather than through a header the server has to trust.

🔗 Related links

🚫 Blocked by

🔍 What does this change?

  • A Cloudflare Access provider that recognizes only Cf-Access-Jwt-Assertion. Authorization: Bearer stays reserved for the OAuth work in BE-728, so a Bearer token is not silently accepted here.
  • The token's email claim resolves to an actor through the Kratos admin API. A credentials-identifier lookup also returns identities whose matching address is still unverified, so requiring verified is what stops someone registering an address they do not control and authenticating as its owner. The unsigned Cf-Access-Authenticated-User-Email header is never consulted.
  • The admin API drops AdminActorId and its JWT extractors, and runs the operator chain — Access JWT, then service delegation. /health stays open; everything else authenticates, and a Kratos session does not.
  • The bulk destructive endpoints (/snapshot, /accounts, /data-types, /property-types, /entity-types) move behind the unsafe-dev-endpoints cargo feature, replacing the runtime flag. The Dockerfile builds without it, so a released image cannot serve them at all; local and CI builds enable it.
  • JWT validation moves from the API crate into the authentication crate, next to the providers that use it. It now requires aud and iss rather than only comparing them when present, and validates nbf. jsonwebtoken requires just exp by default, so a token omitting aud — or carrying it with the wrong type — verified before this; aud is what scopes a token to this application, since every application of an Access team is signed by the same keys.
  • A JWKS entry that cannot be turned into a verification key is reported as a provider fault instead of an invalid token, so a malformed key set surfaces as an error rather than answering every request with 401 at debug level.
  • A failed JWKS fetch is remembered for the cooldown. The cooldown previously bounded nothing until the first fetch had succeeded, so during an outage every request mounted its own fetch and queued behind the fetch mutex.
  • The looked-up email no longer reaches error reports. It travels in a query string, and a reqwest error renders the URL it failed on, which put the address into error-level logs and Sentry on any Kratos fault.
  • The three JWT timing flags reject 0, which silently disabled the refresh cooldown or made every fetch time out.
  • A non-HTTP --kratos-admin-url is rejected at startup instead of aborting the process inside the resolver.
  • Callers that reach the admin API present the service credential: the httpyac suite, the Playwright helper, and the backend integration helper.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are not made in this PR
    • The [Graph Admin API] runbook describes the previous JWT-or-header scheme and the runtime flag. Its access instructions and the endpoint availability table need updating once this lands.

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • affected the execution graph, and the turbo.json's have been updated to reflect this

⚠️ Known issues

The JWT validator accepts a single audience. An Access application with more than one AUD tag would need the configuration widened to a list — a small change, but not one this PR makes since nothing needs it yet.

The admin routes carry no OpenAPI annotations, so they stay absent from the generated spec as before. Worth folding into BE-757 rather than here.

Removing the Authorization: Bearer fallback is a behaviour change for anyone who reached the admin API that way. Nothing in the repo does, but a hand-rolled client might.

A Kratos session no longer authenticates on the admin API. Anything that reached it with a session must present the service credential instead; the callers in this repo already do.

The Access path does not read the identity's Kratos state, while the session path requires an active session. Deactivating an identity would therefore not revoke Access-based entry. Latent today, since users are deleted rather than deactivated — tracked as a follow-up rather than fixed here, because it needs a decision on whether state gates authentication or only the session.

🐾 Next steps

  • BE-764 (internal) — model the public actor properly and drop the provider-side actor lookup, which the policy layer already performs.
  • BE-763 covers rotating the service secret.

🛡 What tests cover this?

  • Unit tests for the Access provider mint tokens against a fake JWKS: algorithm confusion (an HS256 token signed with an attacker-chosen secret), a signature transplanted from another valid token, expiry, wrong audience, wrong issuer, missing and unknown key IDs, and a missing email claim.
  • The JWKS cache is covered by counting fetches at the fake endpoint, which pins the refresh cooldown that stops a crafted kid from driving one fetch per request.
  • Contract tests in tests/kratos.rs run the identity lookup against the live Kratos of the compose stack, covering a verified address, an unverified one, an identity without a Graph actor, an unknown address, and case-insensitive matching. These exist because the unit tests serve their responses from a hand-written fake, which only holds as long as the fake matches Kratos.
  • The httpyac suite covers the admin authentication boundary end to end: a valid Kratos session is rejected there, an actor header without the secret is rejected, a wrong secret is rejected, and /health stays open.
  • Required-claim coverage asserts that an omitted or wrongly-typed aud/iss fails, not only a wrong one — the mutation that removes the requirement turns those cases red.

❓ How to test this?

  1. cargo nextest run --package hash-graph-authentication --package hash-graph-api --all-features
  2. With the compose stack up: yarn workspace @rust/hash-graph-authentication test:integration for the Kratos contract tests
  3. From tests/graph/http/: sh test.sh
  4. Confirm the feature gate: build without --all-features and check that DELETE /data-types on the admin port answers 404 while POST /users/delete still answers 401

@TimDiekmann TimDiekmann self-assigned this Aug 18, 2026
@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 18, 2026 10:56am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Aug 18, 2026 10:56am
petrinaut Skipped Skipped Aug 18, 2026 10:56am

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/apps > hash* Affects HASH (a `hash-*` app) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team area/tests New or updated tests area/tests > integration New or updated integration tests area/tests > playwright New or updated Playwright tests area/apps area/apps > hash-graph labels Aug 18, 2026
@TimDiekmann
TimDiekmann marked this pull request as ready for review August 18, 2026 09:26
Copilot AI balanced review requested due to automatic review settings August 18, 2026 09:26
@TimDiekmann
TimDiekmann deployed to pull-request August 18, 2026 09:26 — with GitHub Actions Active
@TimDiekmann
TimDiekmann deployed to pull-request August 18, 2026 09:26 — with GitHub Actions Active
@cursor

cursor Bot commented Aug 18, 2026

Copy link
Copy Markdown

PR Summary

Cursor Bugbot is generating a summary for commit 7d3b7c8. Configure here.

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.21485% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.17%. Comparing base (315d6d8) to head (6af071c).

Files with missing lines Patch % Lines
libs/@local/graph/authentication/src/provider.rs 0.00% 8 Missing ⚠️
libs/@local/graph/authentication/src/jwt.rs 98.00% 4 Missing and 2 partials ⚠️
libs/@local/graph/authentication/src/cloudflare.rs 98.56% 3 Missing and 1 partial ⚠️
...@local/graph/authentication/src/kratos/identity.rs 98.13% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@                                         Coverage Diff                                          @@
##           t/be-765-tighten-the-authentication-provider-types-and-split-the    #9235      +/-   ##
====================================================================================================
+ Coverage                                                             59.94%   60.17%   +0.22%     
====================================================================================================
  Files                                                                  1427     1430       +3     
  Lines                                                                139885   140522     +637     
  Branches                                                               6582     6596      +14     
====================================================================================================
+ Hits                                                                  83859    84557     +698     
+ Misses                                                                54952    54887      -65     
- Partials                                                               1074     1078       +4     
Flag Coverage Δ
apps.hash-ai-worker-ts 1.99% <ø> (ø)
apps.hash-api 14.00% <ø> (ø)
rust.hash-graph-postgres-store 29.33% <ø> (ø)
rust.hash-graph-validation 84.71% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Cloudflare Access JWT authentication to the Graph API and unifies authentication across the main and admin ports.

Changes:

  • Adds JWT validation and verified-email-to-actor resolution through Kratos.
  • Applies the shared Kratos, Cloudflare, and service-delegation provider chain to both APIs.
  • Feature-gates destructive development endpoints and updates test clients.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.env Removes the obsolete runtime unsafe-auth flag.
apps/hash-graph/Cargo.toml Forwards the unsafe-endpoints feature.
apps/hash-graph/src/subcommand/admin_server.rs Builds authentication configuration for the admin server.
apps/hash-graph/src/subcommand/server.rs Shares authentication setup across both servers.
Cargo.lock Records dependency resolution changes.
libs/@local/graph/api/Cargo.toml Defines the unsafe-endpoints feature.
libs/@local/graph/api/src/rest/admin.rs Protects admin routes with shared authentication.
libs/@local/graph/api/src/rest/auth.rs Constructs the shared provider chain.
libs/@local/graph/api/src/rest/mod.rs Enables Cloudflare authentication on the main API.
libs/@local/graph/authentication/Cargo.toml Adds JWT and async dependencies.
libs/@local/graph/authentication/package.json Separates unit and integration tests.
libs/@local/graph/authentication/src/cloudflare.rs Implements the Cloudflare Access provider.
libs/@local/graph/authentication/src/jwt.rs Provides reusable JWKS-backed JWT validation.
libs/@local/graph/authentication/src/kratos/identity.rs Resolves verified emails to Graph actors.
libs/@local/graph/authentication/src/kratos/mod.rs Exports Kratos identity resolution.
libs/@local/graph/authentication/src/lib.rs Exposes Cloudflare and JWT modules.
libs/@local/graph/authentication/src/provider.rs Supports optional providers in chains.
libs/@local/graph/authentication/src/request.rs Adds Access-specific authentication errors.
libs/@local/graph/authentication/tests/kratos.rs Adds live Kratos contract tests.
tests/graph/http/.httpyac.config.js Injects service credentials for both Graph ports.
tests/graph/http/tests/reset-database.http Adds delegated actor headers to reset requests.
tests/hash-backend-integration/src/tests/admin-server.ts Authenticates integration admin requests.
tests/hash-playwright/tests/shared/delete-user.ts Authenticates Playwright user deletion.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/hash-graph/src/subcommand/admin_server.rs
Comment thread tests/graph/http/.httpyac.config.js
@codspeed-hq

codspeed-hq Bot commented Aug 18, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 98 untouched benchmarks


Comparing t/be-761-authenticate-cloudflare-access-jwts-in-the-graph-api (6af071c) with t/be-765-tighten-the-authentication-provider-types-and-split-the (315d6d8)

Open in CodSpeed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.

Adds Cloudflare Access as a third credential and puts the admin API behind the
same provider chain as the main API, so both ports authenticate identically.

The Access provider recognizes only `Cf-Access-Jwt-Assertion`; `Authorization:
Bearer` stays reserved for the OAuth work. The token's email claim is resolved
through the Kratos admin API, which returns identities whose matching address is
still unverified, so requiring `verified` there is what keeps an unverified
address from authenticating as its owner. The plain
`Cf-Access-Authenticated-User-Email` header carries no signature and is never
consulted.

The admin API drops its own JWT extractors and header-based actor resolution in
favour of the shared chain. The bulk destructive endpoints move behind the
`unsafe-dev-endpoints` cargo feature instead of a runtime flag, so the released
image cannot serve them at all: the Dockerfile builds without the feature, while
local and CI builds enable it. Callers that reach the admin API now present the
service credential.

JWT validation moves out of the API crate into the authentication crate, where
the providers live. Its JWKS cache is covered by counting fetches at a fake
endpoint, which pins the refresh cooldown that stops a crafted `kid` from
driving one fetch per request.
Review of the Access path turned up one live gap and several classification
faults.

`aud` and `iss` were configured but not required. `set_audience` and
`set_issuer` compare a claim only when it is present, and only `exp` is required
by default, so a token omitting either — or setting it to a non-string — verified
with no audience binding at all. Since every application of a Cloudflare Access
team is signed by the same keys, `aud` is what scopes a token to this one.
`nbf` was likewise unvalidated.

The admin API no longer accepts an end-user session. It authenticates with the
operator chain (Access, then service delegation), because its handlers do not
authorize beyond requiring some actor: operators arrive through Access, internal
services through the shared secret. That also drops the Kratos session
configuration from the admin server entirely.

The looked-up email travelled in a query string, and a `reqwest::Error` renders
the URL it failed on, so a Kratos timeout put an address into an error-level log
line and a Sentry event. A JWKS entry that cannot be turned into a verification
key was reported as an invalid token, which answered 401 at debug level and hid
a provider outage behind "your token is invalid"; it is now a provider fault.
A failed JWKS fetch is negatively cached for the cooldown, so an outage no
longer buys one outbound attempt per request.

The three JWT duration flags reject zero, which previously disabled the refresh
cooldown silently.
Copilot AI review requested due to automatic review settings August 18, 2026 10:38
@TimDiekmann
TimDiekmann force-pushed the t/be-761-authenticate-cloudflare-access-jwts-in-the-graph-api branch from 4889222 to 5eed42c Compare August 18, 2026 10:38
@TimDiekmann
TimDiekmann deployed to pull-request August 18, 2026 10:39 — with GitHub Actions Active
@TimDiekmann
TimDiekmann deployed to pull-request August 18, 2026 10:39 — with GitHub Actions Active

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated no new comments.

…oundary

The email resolver extends the admin URL with a path, which a cannot-be-a-base
URL does not support, so `HASH_KRATOS_ADMIN_URL=mailto:…` aborted the process
inside the resolver instead of failing to start. The session provider already
validates its URL this way.

The suite only asserted successful admin calls with the injected secret, so the
middleware could have been removed, or accepted a wrong secret, without a test
failing. It now covers an omitted secret, a wrong secret, and the open health
route on the admin port.
Copilot AI review requested due to automatic review settings August 18, 2026 10:45
@vercel
vercel Bot temporarily deployed to Preview – petrinaut August 18, 2026 10:45 Inactive
@TimDiekmann
TimDiekmann deployed to pull-request August 18, 2026 10:47 — with GitHub Actions Active
@TimDiekmann
TimDiekmann deployed to pull-request August 18, 2026 10:47 — with GitHub Actions Active

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (1)

libs/@local/graph/authentication/src/jwt.rs:488

  • This test never observes how many outbound requests were made: even if last_failure and check_failure_cooldown are removed, all five validations still return Err, so the outage-amplification regression it is meant to prevent would pass. Use a counting JWKS handler that returns 500 and assert it was fetched exactly once during the cooldown.
        let mut failures = 0_u32;
        for attempt in 0..5 {
            if validator
                .validate(&token_with_key_id(&format!("crafted-{attempt}")))
                .await
                .is_err()

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$26.3 \mathrm{ms} \pm 197 \mathrm{μs}\left({\color{gray}-0.149 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.40 \mathrm{ms} \pm 22.2 \mathrm{μs}\left({\color{lightgreen}-6.672 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$12.8 \mathrm{ms} \pm 117 \mathrm{μs}\left({\color{gray}4.03 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$43.7 \mathrm{ms} \pm 374 \mathrm{μs}\left({\color{gray}-0.550 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$16.8 \mathrm{ms} \pm 209 \mathrm{μs}\left({\color{gray}4.33 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$25.2 \mathrm{ms} \pm 218 \mathrm{μs}\left({\color{gray}3.23 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$27.7 \mathrm{ms} \pm 217 \mathrm{μs}\left({\color{gray}3.05 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.76 \mathrm{ms} \pm 28.8 \mathrm{μs}\left({\color{gray}-2.896 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$14.1 \mathrm{ms} \pm 123 \mathrm{μs}\left({\color{lightgreen}-6.875 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.82 \mathrm{ms} \pm 52.2 \mathrm{μs}\left({\color{gray}2.10 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.03 \mathrm{ms} \pm 19.5 \mathrm{μs}\left({\color{gray}-2.372 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.39 \mathrm{ms} \pm 21.0 \mathrm{μs}\left({\color{gray}1.52 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.11 \mathrm{ms} \pm 31.0 \mathrm{μs}\left({\color{gray}-0.974 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.61 \mathrm{ms} \pm 29.0 \mathrm{μs}\left({\color{gray}1.08 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.24 \mathrm{ms} \pm 35.2 \mathrm{μs}\left({\color{gray}2.96 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.37 \mathrm{ms} \pm 36.0 \mathrm{μs}\left({\color{gray}-0.889 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.47 \mathrm{ms} \pm 20.1 \mathrm{μs}\left({\color{gray}-1.770 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.13 \mathrm{ms} \pm 28.3 \mathrm{μs}\left({\color{gray}-0.286 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.68 \mathrm{ms} \pm 12.4 \mathrm{μs}\left({\color{gray}-2.666 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.54 \mathrm{ms} \pm 19.4 \mathrm{μs}\left({\color{gray}0.764 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.77 \mathrm{ms} \pm 34.5 \mathrm{μs}\left({\color{gray}4.44 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$3.01 \mathrm{ms} \pm 20.2 \mathrm{μs}\left({\color{gray}0.608 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.76 \mathrm{ms} \pm 19.0 \mathrm{μs}\left({\color{gray}0.917 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$2.94 \mathrm{ms} \pm 16.3 \mathrm{μs}\left({\color{gray}-0.607 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.02 \mathrm{ms} \pm 19.1 \mathrm{μs}\left({\color{gray}-1.901 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.78 \mathrm{ms} \pm 15.5 \mathrm{μs}\left({\color{gray}-0.403 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$2.99 \mathrm{ms} \pm 13.3 \mathrm{μs}\left({\color{gray}-0.577 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.43 \mathrm{ms} \pm 21.4 \mathrm{μs}\left({\color{gray}0.411 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.04 \mathrm{ms} \pm 21.1 \mathrm{μs}\left({\color{gray}0.281 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.35 \mathrm{ms} \pm 23.3 \mathrm{μs}\left({\color{gray}2.61 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.36 \mathrm{ms} \pm 17.7 \mathrm{μs}\left({\color{gray}0.289 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.96 \mathrm{ms} \pm 13.3 \mathrm{μs}\left({\color{gray}-1.240 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.30 \mathrm{ms} \pm 22.8 \mathrm{μs}\left({\color{gray}-2.347 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$42.6 \mathrm{ms} \pm 277 \mathrm{μs}\left({\color{gray}0.410 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$35.1 \mathrm{ms} \pm 236 \mathrm{μs}\left({\color{gray}3.60 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$36.4 \mathrm{ms} \pm 251 \mathrm{μs}\left({\color{gray}-2.263 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$33.5 \mathrm{ms} \pm 233 \mathrm{μs}\left({\color{gray}2.96 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$44.9 \mathrm{ms} \pm 367 \mathrm{μs}\left({\color{gray}3.97 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$50.1 \mathrm{ms} \pm 355 \mathrm{μs}\left({\color{gray}1.83 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$41.3 \mathrm{ms} \pm 319 \mathrm{μs}\left({\color{gray}0.796 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$96.8 \mathrm{ms} \pm 861 \mathrm{μs}\left({\color{red}5.98 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$34.5 \mathrm{ms} \pm 216 \mathrm{μs}\left({\color{gray}-1.607 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$306 \mathrm{ms} \pm 1.40 \mathrm{ms}\left({\color{red}9.80 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$11.2 \mathrm{ms} \pm 83.0 \mathrm{μs}\left({\color{gray}0.804 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$11.4 \mathrm{ms} \pm 93.9 \mathrm{μs}\left({\color{gray}2.79 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$11.8 \mathrm{ms} \pm 91.4 \mathrm{μs}\left({\color{red}7.48 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$11.1 \mathrm{ms} \pm 83.4 \mathrm{μs}\left({\color{gray}-1.814 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$11.4 \mathrm{ms} \pm 94.3 \mathrm{μs}\left({\color{gray}-1.745 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$11.3 \mathrm{ms} \pm 99.5 \mathrm{μs}\left({\color{gray}1.95 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$11.3 \mathrm{ms} \pm 64.9 \mathrm{μs}\left({\color{gray}-0.304 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$11.2 \mathrm{ms} \pm 67.2 \mathrm{μs}\left({\color{gray}0.399 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$11.8 \mathrm{ms} \pm 95.6 \mathrm{μs}\left({\color{gray}3.04 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$12.0 \mathrm{ms} \pm 109 \mathrm{μs}\left({\color{red}6.91 \mathrm{\%}}\right) $$ 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 $$12.2 \mathrm{ms} \pm 129 \mathrm{μs}\left({\color{red}6.72 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$11.9 \mathrm{ms} \pm 98.7 \mathrm{μs}\left({\color{gray}4.39 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$11.9 \mathrm{ms} \pm 170 \mathrm{μs}\left({\color{gray}1.34 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$12.1 \mathrm{ms} \pm 115 \mathrm{μs}\left({\color{gray}4.22 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$12.0 \mathrm{ms} \pm 78.5 \mathrm{μs}\left({\color{gray}3.09 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$11.7 \mathrm{ms} \pm 109 \mathrm{μs}\left({\color{gray}2.07 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$11.9 \mathrm{ms} \pm 98.6 \mathrm{μs}\left({\color{gray}2.25 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$11.8 \mathrm{ms} \pm 106 \mathrm{μs}\left({\color{gray}1.23 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$11.7 \mathrm{ms} \pm 98.4 \mathrm{μs}\left({\color{gray}2.26 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$8.50 \mathrm{ms} \pm 58.7 \mathrm{μs}\left({\color{gray}-2.346 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$57.5 \mathrm{ms} \pm 430 \mathrm{μs}\left({\color{lightgreen}-10.043 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$111 \mathrm{ms} \pm 670 \mathrm{μs}\left({\color{gray}-1.786 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$68.1 \mathrm{ms} \pm 1.19 \mathrm{ms}\left({\color{gray}1.97 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$78.2 \mathrm{ms} \pm 745 \mathrm{μs}\left({\color{gray}-1.992 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$87.3 \mathrm{ms} \pm 865 \mathrm{μs}\left({\color{gray}-1.022 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$93.3 \mathrm{ms} \pm 658 \mathrm{μs}\left({\color{gray}-2.065 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$44.8 \mathrm{ms} \pm 322 \mathrm{μs}\left({\color{gray}-0.163 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$76.5 \mathrm{ms} \pm 509 \mathrm{μs}\left({\color{gray}3.62 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$52.7 \mathrm{ms} \pm 535 \mathrm{μs}\left({\color{gray}2.43 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$62.6 \mathrm{ms} \pm 561 \mathrm{μs}\left({\color{gray}2.85 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$63.6 \mathrm{ms} \pm 464 \mathrm{μs}\left({\color{gray}-0.990 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$63.1 \mathrm{ms} \pm 509 \mathrm{μs}\left({\color{gray}-1.665 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$126 \mathrm{ms} \pm 777 \mathrm{μs}\left({\color{gray}3.72 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$138 \mathrm{ms} \pm 464 \mathrm{μs}\left({\color{gray}2.74 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$18.7 \mathrm{ms} \pm 130 \mathrm{μs}\left({\color{gray}-0.546 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$553 \mathrm{ms} \pm 1.08 \mathrm{ms}\left({\color{gray}-0.988 \mathrm{\%}}\right) $$ Flame Graph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps > hash-graph area/apps area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) area/tests > integration New or updated integration tests area/tests > playwright New or updated Playwright tests area/tests New or updated tests type/eng > backend Owned by the @backend team

Development

Successfully merging this pull request may close these issues.

2 participants