Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c658fbe
feat: query for what has been requested
indietyp Jun 8, 2026
5f2ca40
chore: checkpoint
indietyp Jun 9, 2026
f710253
feat: checkpoint
indietyp Jun 16, 2026
7263469
chore: remove ice ice baby
indietyp Jun 16, 2026
a9b4470
feat: checkpoint
indietyp Jun 16, 2026
685e89f
feat: checkpoint
indietyp Jun 16, 2026
66086b9
feat: checkpoint
indietyp Jun 16, 2026
932bde4
feat: do not skip parameters
indietyp Jun 17, 2026
24bde94
feat: remove property masking in favour of lateral join
indietyp Jun 17, 2026
c39ed31
feat: more policy into own module
indietyp Jun 17, 2026
5de4ae7
feat: protection translation unit
indietyp Jun 17, 2026
6119c88
feat: checkpoint
indietyp Jun 17, 2026
7517029
feat: authorization code
indietyp Jun 17, 2026
ad84a79
fix: lints
indietyp Jun 17, 2026
a7be103
feat: checkpoint
indietyp Jun 17, 2026
b8351d0
feat: checkpoint
indietyp Jun 17, 2026
e755613
feat: test infrastructure
indietyp Jun 18, 2026
85d93d0
feat: reinforced tests
indietyp Jun 18, 2026
ab93b1d
feat: integration tests
indietyp Jun 18, 2026
5baa253
feat: checkpoint
indietyp Jun 18, 2026
98a61ee
feat: checkpoint
indietyp Jun 18, 2026
80cea9d
feat: hardening
indietyp Jun 18, 2026
6b3a7e4
feat: checkpoint
indietyp Jun 18, 2026
f2db9f2
fix: inject auxiliary requests
indietyp Jun 18, 2026
630270b
fix: suggestions from bugbot
indietyp Jun 18, 2026
46c387c
chore: remove stray file
indietyp Jun 18, 2026
59e82bf
chore: add `Arc` where needed
indietyp Jun 18, 2026
6ca10ba
chore: fix debug assertions
indietyp Jun 18, 2026
d52228d
fix: turborepo
indietyp Jun 19, 2026
f016ea2
fix: openapi
indietyp Jun 19, 2026
caecccb
fix: docs
indietyp Jun 19, 2026
73c2d79
fix: merge conflicts
indietyp Jun 22, 2026
5d90ff3
chore: fix imports
indietyp Jun 23, 2026
1e921af
fix: comment
indietyp Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions apps/hash-graph/src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use multiaddr::{Multiaddr, Protocol};
use regex::Regex;
use reqwest::{Client, Url};
use tokio::{io, net::TcpListener, signal, time::timeout};
use tokio_postgres::NoTls;
use tokio_postgres::{Client as PostgresClient, NoTls};
use tokio_util::{codec::FramedWrite, sync::CancellationToken};
use type_system::ontology::json_schema::DomainValidator;

Expand Down Expand Up @@ -383,15 +383,15 @@ where
/// Starts the main graph API server (REST + optional RPC).
async fn start_server<S>(
pool: S,
postgres: PostgresStorePool,
compiler: Arc<CompilerContext>,
config: ServerConfig,
query_logger: Option<QueryLogger>,
filter_protection: Arc<PropertyProtectionFilterConfig<'static>>,
lifecycle: &ServerLifecycle,
) -> Result<(), Report<GraphError>>
where
S: StorePool + Send + Sync + 'static,
for<'p> S::Store<'p>: RestApiStore + PrincipalStore + PolicyStore,
for<'p> S::Store<'p>: RestApiStore + PrincipalStore + PolicyStore + AsRef<PostgresClient>,
{
let store = Arc::new(pool);
let temporal_client = create_temporal_client(&config.temporal)
Expand All @@ -414,12 +414,12 @@ where

let router = rest_api_router(RestRouterDependencies {
store,
postgres,
temporal_client,
domain_regex: DomainValidator::new(config.allowed_url_domain),
query_logger,
api_config: config.api_config,
compiler,
filter_protection,
});
start_rest_server(router, config.http_address, lifecycle);

Expand Down Expand Up @@ -456,9 +456,9 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report<GraphError>> {
validate_links: !args.config.skip_link_validation,
skip_embedding_creation: args.config.skip_embedding_creation,
filter_protection: if args.config.skip_filter_protection {
PropertyProtectionFilterConfig::new()
Arc::new(PropertyProtectionFilterConfig::new())
} else {
PropertyProtectionFilterConfig::hash_default()
Arc::new(PropertyProtectionFilterConfig::hash_default())
},
},
)
Expand All @@ -478,8 +478,6 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report<GraphError>> {

let lifecycle = ServerLifecycle::new();

let postgres = pool.clone();

if args.embed_admin {
start_admin_server(pool.clone(), args.admin, &lifecycle);
}
Expand All @@ -488,6 +486,8 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report<GraphError>> {
start_type_fetcher(args.type_fetcher.clone(), &lifecycle);
}

let filter_protection = Arc::clone(&pool.settings.filter_protection);

let pool = FetchingPool::new(
pool,
(
Expand Down Expand Up @@ -523,10 +523,10 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report<GraphError>> {

if let Err(error) = start_server(
pool,
postgres,
compiler,
args.config,
query_logger,
filter_protection,
&lifecycle,
)
.await
Expand Down
9 changes: 9 additions & 0 deletions libs/@local/graph/api/openapi/openapi.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 22 additions & 2 deletions libs/@local/graph/api/src/rest/hashql/compile.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use hash_graph_authorization::policies::action::ActionName;
use hashql_ast::error::AstDiagnosticCategory;
use hashql_core::{
heap::{Heap, ResetAllocator as _, Scratch},
heap::{self, Heap, ResetAllocator as _, Scratch},
module::ModuleRegistry,
span::{SpanId, SpanTable},
symbol::sym,
Expand All @@ -16,7 +17,10 @@ use hashql_mir::{
body::Body,
def::{DefId, DefIdVec},
error::MirDiagnosticCategory,
pass::{LowerConfig, execution::ExecutionAnalysisResidual},
pass::{
LowerConfig,
execution::{ExecutionAnalysisResidual, VertexType},
},
};
use hashql_syntax_jexpr::span::Span;

Expand All @@ -29,6 +33,10 @@ pub(crate) struct CodeCompilationArtifact<'heap> {
pub postgres: PreparedQueries<'heap, &'heap Heap>,
}

pub(crate) struct CodeExecutionPermissions<'heap> {
pub actions: heap::Vec<'heap, ActionName>,
}

pub(crate) struct Compilation<'heap> {
pub heap: &'heap Heap,

Expand All @@ -39,6 +47,7 @@ pub(crate) struct Compilation<'heap> {

pub entrypoint: DefId,
pub artifact: CodeCompilationArtifact<'heap>,
pub permissions: CodeExecutionPermissions<'heap>,
}

impl<'heap> Compilation<'heap> {
Expand Down Expand Up @@ -158,6 +167,16 @@ impl<'heap> Compilation<'heap> {
let queries = postgres.compile();
scratch.reset();

let mut actions = heap::Vec::new_in(heap);
for query in queries.iter() {
let action = match query.vertex_type {
VertexType::Entity => ActionName::ViewEntity,
};
actions.push(action);
}

let permissions = CodeExecutionPermissions { actions };

context
.diagnostics
.into_status(())
Expand All @@ -176,6 +195,7 @@ impl<'heap> Compilation<'heap> {
interpreter: bodies,
postgres: queries,
},
permissions,
})
}

Expand Down
120 changes: 119 additions & 1 deletion libs/@local/graph/api/src/rest/hashql/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use alloc::borrow::Cow;
use core::ops::Range;

use axum::response::{Html, IntoResponse as _};
use error_stack::Report;
use hash_graph_authorization::policies::store::error::{ContextCreationError, DetermineActorError};
use hashql_ast::error::AstDiagnosticCategory;
use hashql_core::span::{SpanId, SpanTable};
use hashql_diagnostics::{
DiagnosticCategory, Failure, Sources, Status, Success,
Diagnostic, DiagnosticCategory, Failure, Label, Message, Sources, Status, Success,
category::{TerminalDiagnosticCategory, canonical_category_id},
diagnostic::render::{Format, RenderOptions},
severity::Critical,
Expand Down Expand Up @@ -67,6 +69,122 @@ impl DiagnosticCategory for HashQlDiagnosticCategory {
}
}

pub(crate) fn store_acquire_diagnostic(
report: &Report<impl core::error::Error>,
root_span: SpanId,
) -> Diagnostic<HashQlDiagnosticCategory, SpanId, Critical> {
let mut diagnostic =
Diagnostic::new(HashQlDiagnosticCategory::Infrastructure, Critical::BUG).primary(
Label::new(root_span, "failed to acquire database connection"),
);

diagnostic.add_message(Message::note(
"the query compiled successfully but the server could not open a database connection to \
execute it",
));

log_report(
&mut diagnostic,
report,
"failed to acquire database connection",
);

diagnostic
}

pub(crate) fn authorization_context_diagnostic(
report: &Report<ContextCreationError>,
root_span: SpanId,
) -> Diagnostic<HashQlDiagnosticCategory, SpanId, Critical> {
match report.current_context() {
ContextCreationError::ActorNotFound { actor_id } => {
actor_not_found(report, root_span, actor_id)
}
ContextCreationError::DetermineActor { actor_id } => {
// DetermineActor wraps either ActorNotFound or StoreError.
// Only report "does not exist" when the actor was actually looked up
// and not found; a store error during lookup is infrastructure.
if report
.downcast_ref::<DetermineActorError>()
.is_some_and(|inner| matches!(inner, DetermineActorError::StoreError))
{
authorization_context_failed(report, root_span)
} else {
actor_not_found(report, root_span, actor_id)
}
}
ContextCreationError::BuildPrincipalContext { .. }
| ContextCreationError::BuildEntityTypeContext { .. }
| ContextCreationError::BuildPropertyTypeContext { .. }
| ContextCreationError::BuildDataTypeContext { .. }
| ContextCreationError::BuildEntityContext { .. }
| ContextCreationError::ResolveActorPolicies { .. }
| ContextCreationError::CreatePolicySet
| ContextCreationError::CreatePolicyContext
| ContextCreationError::StoreError => authorization_context_failed(report, root_span),
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

fn actor_not_found(
report: &Report<ContextCreationError>,
root_span: SpanId,
actor_id: &impl core::fmt::Display,
) -> Diagnostic<HashQlDiagnosticCategory, SpanId, Critical> {
let mut diagnostic =
Diagnostic::new(HashQlDiagnosticCategory::Infrastructure, Critical::ERROR).primary(
Label::new(root_span, format!("actor `{actor_id}` does not exist")),
);

diagnostic.add_message(Message::note(
"every request must be authenticated with a valid actor ID; the provided ID does not \
correspond to any known user or machine",
));

log_report(&mut diagnostic, report, "actor not found");

diagnostic
}

fn authorization_context_failed(
report: &Report<ContextCreationError>,
root_span: SpanId,
) -> Diagnostic<HashQlDiagnosticCategory, SpanId, Critical> {
let mut diagnostic =
Diagnostic::new(HashQlDiagnosticCategory::Infrastructure, Critical::BUG).primary(
Label::new(root_span, "failed to build authorization context"),
);

diagnostic.add_message(Message::note(format!(
"the authorization system reported: {}",
report.current_context()
)));

diagnostic.add_message(Message::help(
"the query compiled successfully but the server could not resolve the policies needed to \
authorize execution",
));

log_report(
&mut diagnostic,
report,
"failed to build authorization context",
);

diagnostic
}

fn log_report(
diagnostic: &mut Diagnostic<HashQlDiagnosticCategory, SpanId, Critical>,
report: &Report<impl core::error::Error>,
log_message: &str,
) {
if cfg!(debug_assertions) {
diagnostic.add_message(Message::note(format!("{report:?}")));
} else {
tracing::error!(?report, "{log_message}");
}
}

#[derive(Debug, serde::Serialize)]
struct PointerSpan {
pub range: Range<usize>,
Expand Down
Loading
Loading