Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ebc913d
feat: query for what has been requested
indietyp Jun 8, 2026
8eddfa4
chore: checkpoint
indietyp Jun 9, 2026
833c80f
feat: checkpoint
indietyp Jun 16, 2026
bd36c4a
chore: remove ice ice baby
indietyp Jun 16, 2026
32796a0
feat: checkpoint
indietyp Jun 16, 2026
c2fc785
feat: checkpoint
indietyp Jun 16, 2026
424838c
feat: checkpoint
indietyp Jun 16, 2026
2c518d1
feat: do not skip parameters
indietyp Jun 17, 2026
5268f26
feat: remove property masking in favour of lateral join
indietyp Jun 17, 2026
35e54ae
feat: more policy into own module
indietyp Jun 17, 2026
c7495c8
feat: protection translation unit
indietyp Jun 17, 2026
b41d85f
feat: checkpoint
indietyp Jun 17, 2026
776160e
feat: authorization code
indietyp Jun 17, 2026
aa27b78
fix: lints
indietyp Jun 17, 2026
ed11a3b
feat: checkpoint
indietyp Jun 17, 2026
1179194
feat: checkpoint
indietyp Jun 17, 2026
1f1ffd7
feat: test infrastructure
indietyp Jun 18, 2026
03d09ee
feat: reinforced tests
indietyp Jun 18, 2026
b98cb78
feat: integration tests
indietyp Jun 18, 2026
4cb2f9e
feat: checkpoint
indietyp Jun 18, 2026
438ad8a
feat: checkpoint
indietyp Jun 18, 2026
726e7ec
feat: hardening
indietyp Jun 18, 2026
84617fb
feat: checkpoint
indietyp Jun 18, 2026
d826bb1
fix: inject auxiliary requests
indietyp Jun 18, 2026
eedb223
fix: suggestions from bugbot
indietyp Jun 18, 2026
aede4ec
chore: remove stray file
indietyp Jun 18, 2026
8f37189
chore: add `Arc` where needed
indietyp Jun 18, 2026
fd12da8
chore: fix debug assertions
indietyp Jun 18, 2026
bd541a9
fix: turborepo
indietyp Jun 19, 2026
cc4d35a
fix: openapi
indietyp Jun 19, 2026
cedb103
fix: docs
indietyp Jun 19, 2026
0d27405
fix: merge conflicts
indietyp Jun 22, 2026
0cc12bd
chore: fix imports
indietyp Jun 23, 2026
d64c68f
fix: comment
indietyp Jun 23, 2026
89eb929
chore: add read only
indietyp Jul 7, 2026
452556a
fix: early drop
indietyp Jul 7, 2026
f232ebe
fix: merge
indietyp Jul 8, 2026
e3daf6d
fix: snapshots
indietyp Jul 8, 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 @@ -33,7 +33,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 @@ -418,15 +418,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 @@ -450,13 +450,13 @@ where

let router = rest_api_router(RestRouterDependencies {
store,
postgres,
temporal_client,
embedding_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 @@ -493,9 +493,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 @@ -515,8 +515,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 @@ -525,6 +523,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 @@ -560,10 +560,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