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
6f8c934
feat: query for what has been requested
indietyp Jun 8, 2026
45363c6
chore: checkpoint
indietyp Jun 9, 2026
2a7f198
feat: checkpoint
indietyp Jun 16, 2026
0562b49
chore: remove ice ice baby
indietyp Jun 16, 2026
825e7ce
feat: checkpoint
indietyp Jun 16, 2026
3d15fa9
feat: checkpoint
indietyp Jun 16, 2026
e9e54ba
feat: checkpoint
indietyp Jun 16, 2026
ee74b2a
feat: do not skip parameters
indietyp Jun 17, 2026
57d9aef
feat: remove property masking in favour of lateral join
indietyp Jun 17, 2026
03b42d9
feat: more policy into own module
indietyp Jun 17, 2026
1030c66
feat: protection translation unit
indietyp Jun 17, 2026
b17b154
feat: checkpoint
indietyp Jun 17, 2026
41529f0
feat: authorization code
indietyp Jun 17, 2026
2ee7b26
fix: lints
indietyp Jun 17, 2026
e7c739b
feat: checkpoint
indietyp Jun 17, 2026
cd18344
feat: checkpoint
indietyp Jun 17, 2026
e6f329a
feat: test infrastructure
indietyp Jun 18, 2026
cf01763
feat: reinforced tests
indietyp Jun 18, 2026
5b3f087
feat: integration tests
indietyp Jun 18, 2026
0df1bf5
feat: checkpoint
indietyp Jun 18, 2026
8b45e14
feat: checkpoint
indietyp Jun 18, 2026
c009454
feat: hardening
indietyp Jun 18, 2026
728f044
feat: checkpoint
indietyp Jun 18, 2026
33248b8
fix: inject auxiliary requests
indietyp Jun 18, 2026
3159584
fix: suggestions from bugbot
indietyp Jun 18, 2026
35419cd
chore: remove stray file
indietyp Jun 18, 2026
cba7adf
chore: add `Arc` where needed
indietyp Jun 18, 2026
a63881e
chore: fix debug assertions
indietyp Jun 18, 2026
39602e4
fix: turborepo
indietyp Jun 19, 2026
acc9c16
fix: openapi
indietyp Jun 19, 2026
f1b96c9
fix: docs
indietyp Jun 19, 2026
2675b98
fix: merge conflicts
indietyp Jun 22, 2026
dba3284
chore: fix imports
indietyp Jun 23, 2026
f80f803
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