From b78bc205cb4d5ce3fa09f839404bfd359c971be2 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Mon, 8 Jun 2026 18:38:09 +0200 Subject: [PATCH 01/34] feat: query for what has been requested --- .../graph/api/src/rest/hashql/compile.rs | 43 ++++++++++++++++++- libs/@local/graph/api/src/rest/hashql/mod.rs | 17 ++++++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/libs/@local/graph/api/src/rest/hashql/compile.rs b/libs/@local/graph/api/src/rest/hashql/compile.rs index b56c9738864..cf1a9cde86c 100644 --- a/libs/@local/graph/api/src/rest/hashql/compile.rs +++ b/libs/@local/graph/api/src/rest/hashql/compile.rs @@ -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, @@ -17,11 +18,34 @@ use hashql_mir::{ def::{DefId, DefIdVec}, error::MirDiagnosticCategory, pass::{LowerConfig, execution::ExecutionAnalysisResidual}, + visit::Visitor, }; use hashql_syntax_jexpr::span::Span; use super::error::HashQlDiagnosticCategory; +struct FindActions<'heap> { + actions: heap::Vec<'heap, ActionName>, +} + +impl<'heap> hashql_mir::visit::Visitor<'heap> for FindActions<'heap> { + type Result = Result<(), !>; + + fn visit_graph_read_head( + &mut self, + _: hashql_mir::body::terminator::GraphReadLocation, + head: &hashql_mir::body::terminator::GraphReadHead<'heap>, + ) -> Self::Result { + match head { + hashql_mir::body::terminator::GraphReadHead::Entity { axis: _ } => { + self.actions.push(ActionName::ViewEntity); + } + } + + Ok(()) + } +} + pub(crate) struct CodeCompilationArtifact<'heap> { pub assignment: DefIdVec>, &'heap Heap>, @@ -29,6 +53,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, @@ -39,6 +67,7 @@ pub(crate) struct Compilation<'heap> { pub entrypoint: DefId, pub artifact: CodeCompilationArtifact<'heap>, + pub permissions: CodeExecutionPermissions<'heap>, } impl<'heap> Compilation<'heap> { @@ -135,6 +164,17 @@ impl<'heap> Compilation<'heap> { .map_category(HashQlDiagnosticCategory::Mir) .with_diagnostics(advisories)?; + let mut actions = FindActions { + actions: heap::Vec::new_in(heap), + }; + for body in &bodies { + Ok(()) = actions.visit_body(body) + } + + let permissions = CodeExecutionPermissions { + actions: actions.actions, + }; + // Plan the execution let Success { value: execution, @@ -176,6 +216,7 @@ impl<'heap> Compilation<'heap> { interpreter: bodies, postgres: queries, }, + permissions, }) } diff --git a/libs/@local/graph/api/src/rest/hashql/mod.rs b/libs/@local/graph/api/src/rest/hashql/mod.rs index 62912a8e832..160f5fadcb2 100644 --- a/libs/@local/graph/api/src/rest/hashql/mod.rs +++ b/libs/@local/graph/api/src/rest/hashql/mod.rs @@ -14,8 +14,9 @@ use core::num::NonZero; use std::thread::available_parallelism; use axum::{Extension, Router, response::IntoResponse as _, routing::post}; +use hash_graph_authorization::policies::action::ActionName; use hash_graph_postgres_store::store::PostgresStorePool; -use hash_graph_store::pool::StorePool as _; +use hash_graph_store::pool::StorePool; use hash_temporal_client::TemporalClient; use hashql_core::{ heap::{HeapPool, ScratchPool}, @@ -26,7 +27,7 @@ use hashql_diagnostics::{ severity::Critical, }; use hashql_eval::{error::EvalDiagnosticCategory, orchestrator::Orchestrator}; -use hashql_mir::interpret::Inputs; +use hashql_mir::{body, interpret::Inputs}; use hashql_syntax_jexpr::span::Span; use http::StatusCode; use serde_json::value::RawValue; @@ -38,6 +39,7 @@ use self::{ error::{HashQlDiagnosticCategory, status_to_response}, value::OwnedValue, }; +use super::AuthenticatedUserHeader; use crate::rest::{InteractiveHeader, JsonCompatHeader, json::Json, status::BoxedResponse}; /// Shared resources for HashQL query compilation and execution, created once at server startup. @@ -219,6 +221,7 @@ pub(crate) struct HashQlRequest { request_body = HashQlRequest, tag = "HashQL", params( + ("X-Authenticated-User-Actor-Id" = ActorEntityUuid, Header, description = "The ID of the actor which is used to authorize the request"), ("Interactive" = Option, Header, description = "When true, error responses are rendered as HTML instead of JSON"), ("Json-Compat" = Option, Header, description = "When true, serializes the result as plain JSON values, stripping HashQL-specific type wrappers"), ), @@ -228,14 +231,20 @@ pub(crate) struct HashQlRequest { (status = 500, description = "Internal compiler or database error"), ) )] -pub(crate) async fn query_hashql( +pub(crate) async fn query_hashql( + AuthenticatedUserHeader(actor_id): AuthenticatedUserHeader, + Extension(store_pool): Extension>, + Extension(temporal_client): Extension>>, Extension(compiler): Extension>, Extension(postgres): Extension>, Extension(temporal): Extension>>, InteractiveHeader(interactive): InteractiveHeader, JsonCompatHeader(json_compat): JsonCompatHeader, Json(request): Json, -) -> BoxedResponse { +) -> BoxedResponse +where + S: StorePool + Send + Sync, +{ let exec = ExecutionContext { postgres: (*postgres).clone(), temporal, From e96545242ca80d2d19eca5edd86c030bb4477d25 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 9 Jun 2026 09:17:33 +0200 Subject: [PATCH 02/34] chore: checkpoint --- libs/@local/graph/api/src/rest/hashql/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/@local/graph/api/src/rest/hashql/mod.rs b/libs/@local/graph/api/src/rest/hashql/mod.rs index 160f5fadcb2..f00b43b1f42 100644 --- a/libs/@local/graph/api/src/rest/hashql/mod.rs +++ b/libs/@local/graph/api/src/rest/hashql/mod.rs @@ -14,7 +14,6 @@ use core::num::NonZero; use std::thread::available_parallelism; use axum::{Extension, Router, response::IntoResponse as _, routing::post}; -use hash_graph_authorization::policies::action::ActionName; use hash_graph_postgres_store::store::PostgresStorePool; use hash_graph_store::pool::StorePool; use hash_temporal_client::TemporalClient; From 72fae63c624b70383fbe7e2d80d2eec185494172 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:52:18 +0200 Subject: [PATCH 03/34] feat: checkpoint --- libs/@local/graph/api/src/rest/hashql/mod.rs | 66 +++++++++++++------ libs/@local/graph/api/src/rest/mod.rs | 2 - .../postgres-store/src/store/postgres/mod.rs | 1 + rustc-ice-2026-06-16T10_26_13-75957.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_13-75958.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_17-76258.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_17-76259.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_22-76588.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_22-76589.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_29-76928.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_29-76929.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_33-77252.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_33-77253.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_58-77896.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_26_58-77897.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_27_05-78213.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_27_05-78214.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_27_22-78527.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_27_22-78528.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_28_07-79736.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_28_07-79737.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_28_17-80378.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_28_17-80379.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_31_12-83834.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_31_12-83835.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_31_15-84148.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_31_15-84149.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_31_35-84493.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_31_35-84494.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_32_02-84897.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_32_02-84898.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_32_06-85213.txt | 57 ++++++++++++++++ rustc-ice-2026-06-16T10_32_06-85214.txt | 57 ++++++++++++++++ 33 files changed, 1756 insertions(+), 23 deletions(-) create mode 100644 rustc-ice-2026-06-16T10_26_13-75957.txt create mode 100644 rustc-ice-2026-06-16T10_26_13-75958.txt create mode 100644 rustc-ice-2026-06-16T10_26_17-76258.txt create mode 100644 rustc-ice-2026-06-16T10_26_17-76259.txt create mode 100644 rustc-ice-2026-06-16T10_26_22-76588.txt create mode 100644 rustc-ice-2026-06-16T10_26_22-76589.txt create mode 100644 rustc-ice-2026-06-16T10_26_29-76928.txt create mode 100644 rustc-ice-2026-06-16T10_26_29-76929.txt create mode 100644 rustc-ice-2026-06-16T10_26_33-77252.txt create mode 100644 rustc-ice-2026-06-16T10_26_33-77253.txt create mode 100644 rustc-ice-2026-06-16T10_26_58-77896.txt create mode 100644 rustc-ice-2026-06-16T10_26_58-77897.txt create mode 100644 rustc-ice-2026-06-16T10_27_05-78213.txt create mode 100644 rustc-ice-2026-06-16T10_27_05-78214.txt create mode 100644 rustc-ice-2026-06-16T10_27_22-78527.txt create mode 100644 rustc-ice-2026-06-16T10_27_22-78528.txt create mode 100644 rustc-ice-2026-06-16T10_28_07-79736.txt create mode 100644 rustc-ice-2026-06-16T10_28_07-79737.txt create mode 100644 rustc-ice-2026-06-16T10_28_17-80378.txt create mode 100644 rustc-ice-2026-06-16T10_28_17-80379.txt create mode 100644 rustc-ice-2026-06-16T10_31_12-83834.txt create mode 100644 rustc-ice-2026-06-16T10_31_12-83835.txt create mode 100644 rustc-ice-2026-06-16T10_31_15-84148.txt create mode 100644 rustc-ice-2026-06-16T10_31_15-84149.txt create mode 100644 rustc-ice-2026-06-16T10_31_35-84493.txt create mode 100644 rustc-ice-2026-06-16T10_31_35-84494.txt create mode 100644 rustc-ice-2026-06-16T10_32_02-84897.txt create mode 100644 rustc-ice-2026-06-16T10_32_02-84898.txt create mode 100644 rustc-ice-2026-06-16T10_32_06-85213.txt create mode 100644 rustc-ice-2026-06-16T10_32_06-85214.txt diff --git a/libs/@local/graph/api/src/rest/hashql/mod.rs b/libs/@local/graph/api/src/rest/hashql/mod.rs index f00b43b1f42..bd5f463734f 100644 --- a/libs/@local/graph/api/src/rest/hashql/mod.rs +++ b/libs/@local/graph/api/src/rest/hashql/mod.rs @@ -14,7 +14,12 @@ use core::num::NonZero; use std::thread::available_parallelism; use axum::{Extension, Router, response::IntoResponse as _, routing::post}; -use hash_graph_postgres_store::store::PostgresStorePool; +use hash_graph_authorization::policies::{ + MergePolicies, PolicyComponents, + action::ActionName, + store::{PolicyStore, PrincipalStore}, +}; +use hash_graph_postgres_store::store::{PostgresStorePool, postgres::PostgresClient}; use hash_graph_store::pool::StorePool; use hash_temporal_client::TemporalClient; use hashql_core::{ @@ -26,11 +31,12 @@ use hashql_diagnostics::{ severity::Critical, }; use hashql_eval::{error::EvalDiagnosticCategory, orchestrator::Orchestrator}; -use hashql_mir::{body, interpret::Inputs}; +use hashql_mir::interpret::Inputs; use hashql_syntax_jexpr::span::Span; use http::StatusCode; use serde_json::value::RawValue; use tokio_util::task::LocalPoolHandle; +use type_system::principal::actor::{ActorEntityUuid, ActorId}; use utoipa::OpenApi; use self::{ @@ -71,9 +77,10 @@ impl CompilerContext { } /// Per-request database context. -struct ExecutionContext { - postgres: PostgresStorePool, +struct ExecutionContext { temporal: Option>, + actor_id: ActorEntityUuid, + store: Arc, } /// Controls the response format for a HashQL query. @@ -86,12 +93,15 @@ pub(crate) struct CompilationOutputOptions { /// Compiles and executes a HashQL query, returning the result as a [`Status`]. #[expect(clippy::future_not_send)] -async fn query_local_impl( +async fn query_local_impl( ctx: Arc, - exec: ExecutionContext, + exec: ExecutionContext, spans: &mut SpanTable, query: &[u8], -) -> Status { +) -> Status +where + S: for<'pool> StorePool: AsRef + PrincipalStore + PolicyStore>, +{ // Heap and scratch must be created inside this function because `spawn_pinned` requires // `'static`. Moving them across the spawn boundary isn't possible since they borrow from // the pool guards. @@ -108,11 +118,11 @@ async fn query_local_impl( let context = compilation.context(); let Success { - value: client, + value: store, advisories, } = exec - .postgres - .acquire(exec.temporal) + .store + .acquire(exec.temporal.clone()) .await .map_err(|report| { let mut diagnostic = @@ -131,7 +141,16 @@ async fn query_local_impl( .into_status() .with_diagnostics(advisories)?; - let orchestrator = Orchestrator::new(client, &compilation.artifact.postgres, &context); + let mut policy_components = PolicyComponents::builder(&store).with_actor(exec.actor_id); + policy_components.add_actions( + compilation.permissions.actions.iter().copied(), + MergePolicies::Yes, + ); + let policy_components = policy_components + .await + .map_err(|report| todo!("proper diagnostic"))?; + + let orchestrator = Orchestrator::new(&store, &compilation.artifact.postgres, &context); orchestrator .run(&inputs, compilation.entrypoint, []) .await @@ -144,12 +163,15 @@ async fn query_local_impl( } #[expect(clippy::future_not_send)] -async fn query_local( +async fn query_local( ctx: Arc, - exec: ExecutionContext, + exec: ExecutionContext, query: Arc, options: CompilationOutputOptions, -) -> BoxedResponse { +) -> BoxedResponse +where + S: for<'pool> StorePool: AsRef>, +{ let mut sources = Sources::new(); let source_id = sources.push(Source::new(query.get())); @@ -160,12 +182,15 @@ async fn query_local( } /// Spawns a query onto the local thread pool and awaits the response. -async fn run_query( +async fn run_query( ctx: Arc, - exec: ExecutionContext, + exec: ExecutionContext, query: Arc, options: CompilationOutputOptions, -) -> BoxedResponse { +) -> BoxedResponse +where + S: for<'pool> StorePool: AsRef> + Send + Sync + 'static, +{ // The compiler and interpreter hold references into bump-allocated heaps, making their // futures `!Send`. `spawn_pinned` runs them on a dedicated thread; the returned handle // is `Send` so the HTTP handler can await it normally. @@ -233,20 +258,19 @@ pub(crate) struct HashQlRequest { pub(crate) async fn query_hashql( AuthenticatedUserHeader(actor_id): AuthenticatedUserHeader, Extension(store_pool): Extension>, - Extension(temporal_client): Extension>>, Extension(compiler): Extension>, - Extension(postgres): Extension>, Extension(temporal): Extension>>, InteractiveHeader(interactive): InteractiveHeader, JsonCompatHeader(json_compat): JsonCompatHeader, Json(request): Json, ) -> BoxedResponse where - S: StorePool + Send + Sync, + S: for<'pool> StorePool: AsRef> + Send + Sync + 'static, { let exec = ExecutionContext { - postgres: (*postgres).clone(), temporal, + actor_id, + store: store_pool, }; let options = CompilationOutputOptions { diff --git a/libs/@local/graph/api/src/rest/mod.rs b/libs/@local/graph/api/src/rest/mod.rs index 3ecfa5d27de..3d8d7075b8a 100644 --- a/libs/@local/graph/api/src/rest/mod.rs +++ b/libs/@local/graph/api/src/rest/mod.rs @@ -426,7 +426,6 @@ where S: StorePool + Send + Sync + 'static, { pub store: Arc, - pub postgres: PostgresStorePool, pub temporal_client: Option>, pub domain_regex: DomainValidator, pub query_logger: Option, @@ -477,7 +476,6 @@ where ) .layer(http_tracing_layer::HttpTracingLayer) .layer(Extension(dependencies.store)) - .layer(Extension(Arc::new(dependencies.postgres))) .layer(Extension(dependencies.temporal_client)) .layer(Extension(dependencies.domain_regex)) .layer(Extension(dependencies.api_config)) diff --git a/libs/@local/graph/postgres-store/src/store/postgres/mod.rs b/libs/@local/graph/postgres-store/src/store/postgres/mod.rs index 89d6f84cc77..cb1ee1e29bc 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/mod.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/mod.rs @@ -51,6 +51,7 @@ use hash_status::StatusCode; use hash_temporal_client::TemporalClient; use postgres_types::{Json, ToSql}; use time::OffsetDateTime; +pub use tokio_postgres::Client as PostgresClient; use tokio_postgres::{Client, GenericClient as _, error::SqlState}; use tracing::Instrument as _; use type_system::{ diff --git a/rustc-ice-2026-06-16T10_26_13-75957.txt b/rustc-ice-2026-06-16T10_26_13-75957.txt new file mode 100644 index 00000000000..b2918f4c4cd --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_13-75957.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10a6e3e20 - ::create + 1: 0x1082d3690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10a6f5e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10a6d9dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10a6ce7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10a6db3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10a7c0c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10a89cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x1099f70e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x109a71744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109c07de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10a3557e4 - ::evaluate_obligation + 12: 0x10a355ba0 - ::evaluate_obligation_no_overflow + 13: 0x10a2c03a4 - ::process_trait_obligation + 14: 0x10a373ba4 - ::process_obligation + 15: 0x10a0f9c78 - >::process_obligations:: + 16: 0x108a656ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x1089a17ac - ::check_argument_types + 18: 0x108960234 - ::confirm_builtin_call + 19: 0x108947854 - ::check_expr_kind + 20: 0x10895d010 - ::check_expr_with_expectation_and_args + 21: 0x1089a1be0 - ::check_argument_types + 22: 0x1089450c4 - ::check_expr_kind + 23: 0x10895d010 - ::check_expr_with_expectation_and_args + 24: 0x10899db18 - ::check_expr_block + 25: 0x10895d010 - ::check_expr_with_expectation_and_args + 26: 0x10895afd4 - ::check_return_or_body_tail + 27: 0x108a37fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x1089042dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10892db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x109a145c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x109bef2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x1084dd154 - ::typeck:: + 33: 0x1085b890c - ::par_hir_body_owners::::{closure#0} + 34: 0x1085f384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108ce7538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x109a37e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109c26510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x108289f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x1082d27d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1082c96d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x1082d9ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10a700850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_13-75958.txt b/rustc-ice-2026-06-16T10_26_13-75958.txt new file mode 100644 index 00000000000..324dde9c512 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_13-75958.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x109e2fe20 - ::create + 1: 0x107a1f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x109e41e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x109e25dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x109e1a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x109e273d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x109f0cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x109fe8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x1091430e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x1091bd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109353de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x109aa17e4 - ::evaluate_obligation + 12: 0x109aa1ba0 - ::evaluate_obligation_no_overflow + 13: 0x109a0c3a4 - ::process_trait_obligation + 14: 0x109abfba4 - ::process_obligation + 15: 0x109845c78 - >::process_obligations:: + 16: 0x1081b16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x1080ed7ac - ::check_argument_types + 18: 0x1080ac234 - ::confirm_builtin_call + 19: 0x108093854 - ::check_expr_kind + 20: 0x1080a9010 - ::check_expr_with_expectation_and_args + 21: 0x1080edbe0 - ::check_argument_types + 22: 0x1080910c4 - ::check_expr_kind + 23: 0x1080a9010 - ::check_expr_with_expectation_and_args + 24: 0x1080e9b18 - ::check_expr_block + 25: 0x1080a9010 - ::check_expr_with_expectation_and_args + 26: 0x1080a6fd4 - ::check_return_or_body_tail + 27: 0x108183fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x1080502dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x108079b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x1091605c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10933b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107c29154 - ::typeck:: + 33: 0x107d0490c - ::par_hir_body_owners::::{closure#0} + 34: 0x107d3f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108433538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x109183e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109372510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x1079d5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x107a1e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x107a156d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x107a25ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x109e4c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_17-76258.txt b/rustc-ice-2026-06-16T10_26_17-76258.txt new file mode 100644 index 00000000000..47c7803bf36 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_17-76258.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10b84fe20 - ::create + 1: 0x10943f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10b861e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10b845dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10b83a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10b8473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10b92cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10ba08fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10ab630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10abdd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10ad73de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10b4c17e4 - ::evaluate_obligation + 12: 0x10b4c1ba0 - ::evaluate_obligation_no_overflow + 13: 0x10b42c3a4 - ::process_trait_obligation + 14: 0x10b4dfba4 - ::process_obligation + 15: 0x10b265c78 - >::process_obligations:: + 16: 0x109bd16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x109b0d7ac - ::check_argument_types + 18: 0x109acc234 - ::confirm_builtin_call + 19: 0x109ab3854 - ::check_expr_kind + 20: 0x109ac9010 - ::check_expr_with_expectation_and_args + 21: 0x109b0dbe0 - ::check_argument_types + 22: 0x109ab10c4 - ::check_expr_kind + 23: 0x109ac9010 - ::check_expr_with_expectation_and_args + 24: 0x109b09b18 - ::check_expr_block + 25: 0x109ac9010 - ::check_expr_with_expectation_and_args + 26: 0x109ac6fd4 - ::check_return_or_body_tail + 27: 0x109ba3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x109a702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x109a99b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10ab805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10ad5b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x109649154 - ::typeck:: + 33: 0x10972490c - ::par_hir_body_owners::::{closure#0} + 34: 0x10975f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x109e53538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10aba3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10ad92510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x1093f5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10943e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1094356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x109445ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10b86c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_17-76259.txt b/rustc-ice-2026-06-16T10_26_17-76259.txt new file mode 100644 index 00000000000..5c24d26c359 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_17-76259.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10c04fe20 - ::create + 1: 0x109c3f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10c061e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10c045dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10c03a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10c0473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c12cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c208fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10b3630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10b3dd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10b573de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10bcc17e4 - ::evaluate_obligation + 12: 0x10bcc1ba0 - ::evaluate_obligation_no_overflow + 13: 0x10bc2c3a4 - ::process_trait_obligation + 14: 0x10bcdfba4 - ::process_obligation + 15: 0x10ba65c78 - >::process_obligations:: + 16: 0x10a3d16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a30d7ac - ::check_argument_types + 18: 0x10a2cc234 - ::confirm_builtin_call + 19: 0x10a2b3854 - ::check_expr_kind + 20: 0x10a2c9010 - ::check_expr_with_expectation_and_args + 21: 0x10a30dbe0 - ::check_argument_types + 22: 0x10a2b10c4 - ::check_expr_kind + 23: 0x10a2c9010 - ::check_expr_with_expectation_and_args + 24: 0x10a309b18 - ::check_expr_block + 25: 0x10a2c9010 - ::check_expr_with_expectation_and_args + 26: 0x10a2c6fd4 - ::check_return_or_body_tail + 27: 0x10a3a3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a2702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a299b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10b3805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10b55b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x109e49154 - ::typeck:: + 33: 0x109f2490c - ::par_hir_body_owners::::{closure#0} + 34: 0x109f5f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10a653538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10b3a3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10b592510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x109bf5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x109c3e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x109c356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x109c45ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10c06c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_22-76588.txt b/rustc-ice-2026-06-16T10_26_22-76588.txt new file mode 100644 index 00000000000..6e61a0bbdde --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_22-76588.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x109d4fe20 - ::create + 1: 0x10793f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x109d61e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x109d45dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x109d3a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x109d473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x109e2cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x109f08fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x1090630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x1090dd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109273de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x1099c17e4 - ::evaluate_obligation + 12: 0x1099c1ba0 - ::evaluate_obligation_no_overflow + 13: 0x10992c3a4 - ::process_trait_obligation + 14: 0x1099dfba4 - ::process_obligation + 15: 0x109765c78 - >::process_obligations:: + 16: 0x1080d16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10800d7ac - ::check_argument_types + 18: 0x107fcc234 - ::confirm_builtin_call + 19: 0x107fb3854 - ::check_expr_kind + 20: 0x107fc9010 - ::check_expr_with_expectation_and_args + 21: 0x10800dbe0 - ::check_argument_types + 22: 0x107fb10c4 - ::check_expr_kind + 23: 0x107fc9010 - ::check_expr_with_expectation_and_args + 24: 0x108009b18 - ::check_expr_block + 25: 0x107fc9010 - ::check_expr_with_expectation_and_args + 26: 0x107fc6fd4 - ::check_return_or_body_tail + 27: 0x1080a3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x107f702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x107f99b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x1090805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10925b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107b49154 - ::typeck:: + 33: 0x107c2490c - ::par_hir_body_owners::::{closure#0} + 34: 0x107c5f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108353538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x1090a3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109292510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x1078f5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10793e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1079356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x107945ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x109d6c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_22-76589.txt b/rustc-ice-2026-06-16T10_26_22-76589.txt new file mode 100644 index 00000000000..0e64ba1920e --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_22-76589.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10c587e20 - ::create + 1: 0x10a177690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10c599e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10c57ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10c5727c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10c57f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c664c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c740fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10b89b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10b915744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10baabde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10c1f97e4 - ::evaluate_obligation + 12: 0x10c1f9ba0 - ::evaluate_obligation_no_overflow + 13: 0x10c1643a4 - ::process_trait_obligation + 14: 0x10c217ba4 - ::process_obligation + 15: 0x10bf9dc78 - >::process_obligations:: + 16: 0x10a9096ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a8457ac - ::check_argument_types + 18: 0x10a804234 - ::confirm_builtin_call + 19: 0x10a7eb854 - ::check_expr_kind + 20: 0x10a801010 - ::check_expr_with_expectation_and_args + 21: 0x10a845be0 - ::check_argument_types + 22: 0x10a7e90c4 - ::check_expr_kind + 23: 0x10a801010 - ::check_expr_with_expectation_and_args + 24: 0x10a841b18 - ::check_expr_block + 25: 0x10a801010 - ::check_expr_with_expectation_and_args + 26: 0x10a7fefd4 - ::check_return_or_body_tail + 27: 0x10a8dbfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a7a82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a7d1b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10b8b85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10ba932ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10a381154 - ::typeck:: + 33: 0x10a45c90c - ::par_hir_body_owners::::{closure#0} + 34: 0x10a49784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10ab8b538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10b8dbe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10baca510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10a12df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10a1767d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10a16d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10a17dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10c5a4850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_29-76928.txt b/rustc-ice-2026-06-16T10_26_29-76928.txt new file mode 100644 index 00000000000..2a43c7ffa46 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_29-76928.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10990fe20 - ::create + 1: 0x1074ff690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x109921e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x109905dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x1098fa7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x1099073d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x1099ecc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x109ac8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x108c230e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x108c9d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x108e33de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x1095817e4 - ::evaluate_obligation + 12: 0x109581ba0 - ::evaluate_obligation_no_overflow + 13: 0x1094ec3a4 - ::process_trait_obligation + 14: 0x10959fba4 - ::process_obligation + 15: 0x109325c78 - >::process_obligations:: + 16: 0x107c916ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x107bcd7ac - ::check_argument_types + 18: 0x107b8c234 - ::confirm_builtin_call + 19: 0x107b73854 - ::check_expr_kind + 20: 0x107b89010 - ::check_expr_with_expectation_and_args + 21: 0x107bcdbe0 - ::check_argument_types + 22: 0x107b710c4 - ::check_expr_kind + 23: 0x107b89010 - ::check_expr_with_expectation_and_args + 24: 0x107bc9b18 - ::check_expr_block + 25: 0x107b89010 - ::check_expr_with_expectation_and_args + 26: 0x107b86fd4 - ::check_return_or_body_tail + 27: 0x107c63fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x107b302dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x107b59b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x108c405c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x108e1b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107709154 - ::typeck:: + 33: 0x1077e490c - ::par_hir_body_owners::::{closure#0} + 34: 0x10781f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x107f13538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x108c63e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x108e52510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x1074b5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x1074fe7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1074f56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x107505ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10992c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_29-76929.txt b/rustc-ice-2026-06-16T10_26_29-76929.txt new file mode 100644 index 00000000000..d4d6d96d9b7 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_29-76929.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x109b93e20 - ::create + 1: 0x107783690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x109ba5e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x109b89dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x109b7e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x109b8b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x109c70c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x109d4cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x108ea70e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x108f21744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x1090b7de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x1098057e4 - ::evaluate_obligation + 12: 0x109805ba0 - ::evaluate_obligation_no_overflow + 13: 0x1097703a4 - ::process_trait_obligation + 14: 0x109823ba4 - ::process_obligation + 15: 0x1095a9c78 - >::process_obligations:: + 16: 0x107f156ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x107e517ac - ::check_argument_types + 18: 0x107e10234 - ::confirm_builtin_call + 19: 0x107df7854 - ::check_expr_kind + 20: 0x107e0d010 - ::check_expr_with_expectation_and_args + 21: 0x107e51be0 - ::check_argument_types + 22: 0x107df50c4 - ::check_expr_kind + 23: 0x107e0d010 - ::check_expr_with_expectation_and_args + 24: 0x107e4db18 - ::check_expr_block + 25: 0x107e0d010 - ::check_expr_with_expectation_and_args + 26: 0x107e0afd4 - ::check_return_or_body_tail + 27: 0x107ee7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x107db42dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x107dddb44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x108ec45c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10909f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10798d154 - ::typeck:: + 33: 0x107a6890c - ::par_hir_body_owners::::{closure#0} + 34: 0x107aa384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108197538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x108ee7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x1090d6510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x107739f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x1077827d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1077796d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x107789ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x109bb0850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_33-77252.txt b/rustc-ice-2026-06-16T10_26_33-77252.txt new file mode 100644 index 00000000000..e7689de5979 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_33-77252.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10a58be20 - ::create + 1: 0x10817b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10a59de30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10a581dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10a5767c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10a5833d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10a668c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10a744fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10989f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x109919744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109aafde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10a1fd7e4 - ::evaluate_obligation + 12: 0x10a1fdba0 - ::evaluate_obligation_no_overflow + 13: 0x10a1683a4 - ::process_trait_obligation + 14: 0x10a21bba4 - ::process_obligation + 15: 0x109fa1c78 - >::process_obligations:: + 16: 0x10890d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x1088497ac - ::check_argument_types + 18: 0x108808234 - ::confirm_builtin_call + 19: 0x1087ef854 - ::check_expr_kind + 20: 0x108805010 - ::check_expr_with_expectation_and_args + 21: 0x108849be0 - ::check_argument_types + 22: 0x1087ed0c4 - ::check_expr_kind + 23: 0x108805010 - ::check_expr_with_expectation_and_args + 24: 0x108845b18 - ::check_expr_block + 25: 0x108805010 - ::check_expr_with_expectation_and_args + 26: 0x108802fd4 - ::check_return_or_body_tail + 27: 0x1088dffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x1087ac2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x1087d5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x1098bc5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x109a972ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x108385154 - ::typeck:: + 33: 0x10846090c - ::par_hir_body_owners::::{closure#0} + 34: 0x10849b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108b8f538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x1098dfe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109ace510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x108131f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10817a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1081716d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x108181ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10a5a8850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_33-77253.txt b/rustc-ice-2026-06-16T10_26_33-77253.txt new file mode 100644 index 00000000000..88dbb6f5d1e --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_33-77253.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10c857e20 - ::create + 1: 0x10a447690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10c869e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10c84ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10c8427c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10c84f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c934c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10ca10fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10bb6b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10bbe5744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10bd7bde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10c4c97e4 - ::evaluate_obligation + 12: 0x10c4c9ba0 - ::evaluate_obligation_no_overflow + 13: 0x10c4343a4 - ::process_trait_obligation + 14: 0x10c4e7ba4 - ::process_obligation + 15: 0x10c26dc78 - >::process_obligations:: + 16: 0x10abd96ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10ab157ac - ::check_argument_types + 18: 0x10aad4234 - ::confirm_builtin_call + 19: 0x10aabb854 - ::check_expr_kind + 20: 0x10aad1010 - ::check_expr_with_expectation_and_args + 21: 0x10ab15be0 - ::check_argument_types + 22: 0x10aab90c4 - ::check_expr_kind + 23: 0x10aad1010 - ::check_expr_with_expectation_and_args + 24: 0x10ab11b18 - ::check_expr_block + 25: 0x10aad1010 - ::check_expr_with_expectation_and_args + 26: 0x10aacefd4 - ::check_return_or_body_tail + 27: 0x10ababfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10aa782dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10aaa1b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10bb885c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10bd632ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10a651154 - ::typeck:: + 33: 0x10a72c90c - ::par_hir_body_owners::::{closure#0} + 34: 0x10a76784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10ae5b538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10bbabe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10bd9a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10a3fdf8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10a4467d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10a43d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10a44dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10c874850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_58-77896.txt b/rustc-ice-2026-06-16T10_26_58-77896.txt new file mode 100644 index 00000000000..7091332b816 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_58-77896.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10c727e20 - ::create + 1: 0x10a317690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10c739e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10c71ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10c7127c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10c71f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c804c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c8e0fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10ba3b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10bab5744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10bc4bde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10c3997e4 - ::evaluate_obligation + 12: 0x10c399ba0 - ::evaluate_obligation_no_overflow + 13: 0x10c3043a4 - ::process_trait_obligation + 14: 0x10c3b7ba4 - ::process_obligation + 15: 0x10c13dc78 - >::process_obligations:: + 16: 0x10aaa96ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a9e57ac - ::check_argument_types + 18: 0x10a9a4234 - ::confirm_builtin_call + 19: 0x10a98b854 - ::check_expr_kind + 20: 0x10a9a1010 - ::check_expr_with_expectation_and_args + 21: 0x10a9e5be0 - ::check_argument_types + 22: 0x10a9890c4 - ::check_expr_kind + 23: 0x10a9a1010 - ::check_expr_with_expectation_and_args + 24: 0x10a9e1b18 - ::check_expr_block + 25: 0x10a9a1010 - ::check_expr_with_expectation_and_args + 26: 0x10a99efd4 - ::check_return_or_body_tail + 27: 0x10aa7bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a9482dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a971b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10ba585c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10bc332ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10a521154 - ::typeck:: + 33: 0x10a5fc90c - ::par_hir_body_owners::::{closure#0} + 34: 0x10a63784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10ad2b538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10ba7be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10bc6a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10a2cdf8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10a3167d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10a30d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10a31dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10c744850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_26_58-77897.txt b/rustc-ice-2026-06-16T10_26_58-77897.txt new file mode 100644 index 00000000000..5a68d120006 --- /dev/null +++ b/rustc-ice-2026-06-16T10_26_58-77897.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10a01fe20 - ::create + 1: 0x107c0f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10a031e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10a015dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10a00a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10a0173d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10a0fcc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10a1d8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x1093330e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x1093ad744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109543de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x109c917e4 - ::evaluate_obligation + 12: 0x109c91ba0 - ::evaluate_obligation_no_overflow + 13: 0x109bfc3a4 - ::process_trait_obligation + 14: 0x109cafba4 - ::process_obligation + 15: 0x109a35c78 - >::process_obligations:: + 16: 0x1083a16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x1082dd7ac - ::check_argument_types + 18: 0x10829c234 - ::confirm_builtin_call + 19: 0x108283854 - ::check_expr_kind + 20: 0x108299010 - ::check_expr_with_expectation_and_args + 21: 0x1082ddbe0 - ::check_argument_types + 22: 0x1082810c4 - ::check_expr_kind + 23: 0x108299010 - ::check_expr_with_expectation_and_args + 24: 0x1082d9b18 - ::check_expr_block + 25: 0x108299010 - ::check_expr_with_expectation_and_args + 26: 0x108296fd4 - ::check_return_or_body_tail + 27: 0x108373fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x1082402dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x108269b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x1093505c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10952b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107e19154 - ::typeck:: + 33: 0x107ef490c - ::par_hir_body_owners::::{closure#0} + 34: 0x107f2f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108623538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x109373e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109562510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x107bc5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x107c0e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x107c056d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x107c15ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10a03c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_27_05-78213.txt b/rustc-ice-2026-06-16T10_27_05-78213.txt new file mode 100644 index 00000000000..b5b0501ab36 --- /dev/null +++ b/rustc-ice-2026-06-16T10_27_05-78213.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x109b77e20 - ::create + 1: 0x107767690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x109b89e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x109b6ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x109b627c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x109b6f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x109c54c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x109d30fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x108e8b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x108f05744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10909bde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x1097e97e4 - ::evaluate_obligation + 12: 0x1097e9ba0 - ::evaluate_obligation_no_overflow + 13: 0x1097543a4 - ::process_trait_obligation + 14: 0x109807ba4 - ::process_obligation + 15: 0x10958dc78 - >::process_obligations:: + 16: 0x107ef96ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x107e357ac - ::check_argument_types + 18: 0x107df4234 - ::confirm_builtin_call + 19: 0x107ddb854 - ::check_expr_kind + 20: 0x107df1010 - ::check_expr_with_expectation_and_args + 21: 0x107e35be0 - ::check_argument_types + 22: 0x107dd90c4 - ::check_expr_kind + 23: 0x107df1010 - ::check_expr_with_expectation_and_args + 24: 0x107e31b18 - ::check_expr_block + 25: 0x107df1010 - ::check_expr_with_expectation_and_args + 26: 0x107deefd4 - ::check_return_or_body_tail + 27: 0x107ecbfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x107d982dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x107dc1b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x108ea85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x1090832ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107971154 - ::typeck:: + 33: 0x107a4c90c - ::par_hir_body_owners::::{closure#0} + 34: 0x107a8784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10817b538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x108ecbe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x1090ba510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10771df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x1077667d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10775d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10776dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x109b94850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_27_05-78214.txt b/rustc-ice-2026-06-16T10_27_05-78214.txt new file mode 100644 index 00000000000..6d78247e121 --- /dev/null +++ b/rustc-ice-2026-06-16T10_27_05-78214.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x109c8be20 - ::create + 1: 0x10787b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x109c9de30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x109c81dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x109c767c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x109c833d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x109d68c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x109e44fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x108f9f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x109019744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x1091afde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x1098fd7e4 - ::evaluate_obligation + 12: 0x1098fdba0 - ::evaluate_obligation_no_overflow + 13: 0x1098683a4 - ::process_trait_obligation + 14: 0x10991bba4 - ::process_obligation + 15: 0x1096a1c78 - >::process_obligations:: + 16: 0x10800d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x107f497ac - ::check_argument_types + 18: 0x107f08234 - ::confirm_builtin_call + 19: 0x107eef854 - ::check_expr_kind + 20: 0x107f05010 - ::check_expr_with_expectation_and_args + 21: 0x107f49be0 - ::check_argument_types + 22: 0x107eed0c4 - ::check_expr_kind + 23: 0x107f05010 - ::check_expr_with_expectation_and_args + 24: 0x107f45b18 - ::check_expr_block + 25: 0x107f05010 - ::check_expr_with_expectation_and_args + 26: 0x107f02fd4 - ::check_return_or_body_tail + 27: 0x107fdffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x107eac2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x107ed5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x108fbc5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x1091972ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107a85154 - ::typeck:: + 33: 0x107b6090c - ::par_hir_body_owners::::{closure#0} + 34: 0x107b9b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10828f538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x108fdfe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x1091ce510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x107831f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10787a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1078716d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x107881ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x109ca8850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_27_22-78527.txt b/rustc-ice-2026-06-16T10_27_22-78527.txt new file mode 100644 index 00000000000..31d7a14d076 --- /dev/null +++ b/rustc-ice-2026-06-16T10_27_22-78527.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10a8efe20 - ::create + 1: 0x1084df690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10a901e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10a8e5dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10a8da7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10a8e73d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10a9ccc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10aaa8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x109c030e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x109c7d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109e13de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10a5617e4 - ::evaluate_obligation + 12: 0x10a561ba0 - ::evaluate_obligation_no_overflow + 13: 0x10a4cc3a4 - ::process_trait_obligation + 14: 0x10a57fba4 - ::process_obligation + 15: 0x10a305c78 - >::process_obligations:: + 16: 0x108c716ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x108bad7ac - ::check_argument_types + 18: 0x108b6c234 - ::confirm_builtin_call + 19: 0x108b53854 - ::check_expr_kind + 20: 0x108b69010 - ::check_expr_with_expectation_and_args + 21: 0x108badbe0 - ::check_argument_types + 22: 0x108b510c4 - ::check_expr_kind + 23: 0x108b69010 - ::check_expr_with_expectation_and_args + 24: 0x108ba9b18 - ::check_expr_block + 25: 0x108b69010 - ::check_expr_with_expectation_and_args + 26: 0x108b66fd4 - ::check_return_or_body_tail + 27: 0x108c43fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x108b102dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x108b39b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x109c205c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x109dfb2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x1086e9154 - ::typeck:: + 33: 0x1087c490c - ::par_hir_body_owners::::{closure#0} + 34: 0x1087ff84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108ef3538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x109c43e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109e32510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x108495f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x1084de7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1084d56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x1084e5ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10a90c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_27_22-78528.txt b/rustc-ice-2026-06-16T10_27_22-78528.txt new file mode 100644 index 00000000000..e7d5dad8b40 --- /dev/null +++ b/rustc-ice-2026-06-16T10_27_22-78528.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10dcdfe20 - ::create + 1: 0x10b8cf690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10dcf1e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10dcd5dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10dcca7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10dcd73d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10ddbcc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10de98fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10cff30e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10d06d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10d203de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10d9517e4 - ::evaluate_obligation + 12: 0x10d951ba0 - ::evaluate_obligation_no_overflow + 13: 0x10d8bc3a4 - ::process_trait_obligation + 14: 0x10d96fba4 - ::process_obligation + 15: 0x10d6f5c78 - >::process_obligations:: + 16: 0x10c0616ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10bf9d7ac - ::check_argument_types + 18: 0x10bf5c234 - ::confirm_builtin_call + 19: 0x10bf43854 - ::check_expr_kind + 20: 0x10bf59010 - ::check_expr_with_expectation_and_args + 21: 0x10bf9dbe0 - ::check_argument_types + 22: 0x10bf410c4 - ::check_expr_kind + 23: 0x10bf59010 - ::check_expr_with_expectation_and_args + 24: 0x10bf99b18 - ::check_expr_block + 25: 0x10bf59010 - ::check_expr_with_expectation_and_args + 26: 0x10bf56fd4 - ::check_return_or_body_tail + 27: 0x10c033fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10bf002dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10bf29b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10d0105c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10d1eb2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10bad9154 - ::typeck:: + 33: 0x10bbb490c - ::par_hir_body_owners::::{closure#0} + 34: 0x10bbef84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10c2e3538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10d033e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10d222510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10b885f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10b8ce7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10b8c56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10b8d5ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10dcfc850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_28_07-79736.txt b/rustc-ice-2026-06-16T10_28_07-79736.txt new file mode 100644 index 00000000000..1464294d68b --- /dev/null +++ b/rustc-ice-2026-06-16T10_28_07-79736.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10bf73e20 - ::create + 1: 0x109b63690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10bf85e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10bf69dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10bf5e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10bf6b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c050c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c12cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10b2870e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10b301744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10b497de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10bbe57e4 - ::evaluate_obligation + 12: 0x10bbe5ba0 - ::evaluate_obligation_no_overflow + 13: 0x10bb503a4 - ::process_trait_obligation + 14: 0x10bc03ba4 - ::process_obligation + 15: 0x10b989c78 - >::process_obligations:: + 16: 0x10a2f56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a2317ac - ::check_argument_types + 18: 0x10a1f0234 - ::confirm_builtin_call + 19: 0x10a1d7854 - ::check_expr_kind + 20: 0x10a1ed010 - ::check_expr_with_expectation_and_args + 21: 0x10a231be0 - ::check_argument_types + 22: 0x10a1d50c4 - ::check_expr_kind + 23: 0x10a1ed010 - ::check_expr_with_expectation_and_args + 24: 0x10a22db18 - ::check_expr_block + 25: 0x10a1ed010 - ::check_expr_with_expectation_and_args + 26: 0x10a1eafd4 - ::check_return_or_body_tail + 27: 0x10a2c7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a1942dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a1bdb44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10b2a45c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10b47f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x109d6d154 - ::typeck:: + 33: 0x109e4890c - ::par_hir_body_owners::::{closure#0} + 34: 0x109e8384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10a577538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10b2c7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10b4b6510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x109b19f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x109b627d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x109b596d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x109b69ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10bf90850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_28_07-79737.txt b/rustc-ice-2026-06-16T10_28_07-79737.txt new file mode 100644 index 00000000000..2ca44c868a4 --- /dev/null +++ b/rustc-ice-2026-06-16T10_28_07-79737.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10a43be20 - ::create + 1: 0x10802b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10a44de30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10a431dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10a4267c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10a4333d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10a518c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10a5f4fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10974f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x1097c9744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10995fde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10a0ad7e4 - ::evaluate_obligation + 12: 0x10a0adba0 - ::evaluate_obligation_no_overflow + 13: 0x10a0183a4 - ::process_trait_obligation + 14: 0x10a0cbba4 - ::process_obligation + 15: 0x109e51c78 - >::process_obligations:: + 16: 0x1087bd6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x1086f97ac - ::check_argument_types + 18: 0x1086b8234 - ::confirm_builtin_call + 19: 0x10869f854 - ::check_expr_kind + 20: 0x1086b5010 - ::check_expr_with_expectation_and_args + 21: 0x1086f9be0 - ::check_argument_types + 22: 0x10869d0c4 - ::check_expr_kind + 23: 0x1086b5010 - ::check_expr_with_expectation_and_args + 24: 0x1086f5b18 - ::check_expr_block + 25: 0x1086b5010 - ::check_expr_with_expectation_and_args + 26: 0x1086b2fd4 - ::check_return_or_body_tail + 27: 0x10878ffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10865c2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x108685b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10976c5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x1099472ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x108235154 - ::typeck:: + 33: 0x10831090c - ::par_hir_body_owners::::{closure#0} + 34: 0x10834b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108a3f538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10978fe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10997e510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x107fe1f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10802a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1080216d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x108031ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10a458850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_28_17-80378.txt b/rustc-ice-2026-06-16T10_28_17-80378.txt new file mode 100644 index 00000000000..1258b3ffa87 --- /dev/null +++ b/rustc-ice-2026-06-16T10_28_17-80378.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10e843e20 - ::create + 1: 0x10c433690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10e855e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10e839dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10e82e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10e83b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10e920c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10e9fcfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10db570e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10dbd1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10dd67de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10e4b57e4 - ::evaluate_obligation + 12: 0x10e4b5ba0 - ::evaluate_obligation_no_overflow + 13: 0x10e4203a4 - ::process_trait_obligation + 14: 0x10e4d3ba4 - ::process_obligation + 15: 0x10e259c78 - >::process_obligations:: + 16: 0x10cbc56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10cb017ac - ::check_argument_types + 18: 0x10cac0234 - ::confirm_builtin_call + 19: 0x10caa7854 - ::check_expr_kind + 20: 0x10cabd010 - ::check_expr_with_expectation_and_args + 21: 0x10cb01be0 - ::check_argument_types + 22: 0x10caa50c4 - ::check_expr_kind + 23: 0x10cabd010 - ::check_expr_with_expectation_and_args + 24: 0x10cafdb18 - ::check_expr_block + 25: 0x10cabd010 - ::check_expr_with_expectation_and_args + 26: 0x10cabafd4 - ::check_return_or_body_tail + 27: 0x10cb97fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10ca642dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10ca8db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10db745c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10dd4f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10c63d154 - ::typeck:: + 33: 0x10c71890c - ::par_hir_body_owners::::{closure#0} + 34: 0x10c75384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10ce47538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10db97e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10dd86510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10c3e9f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10c4327d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10c4296d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10c439ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10e860850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_28_17-80379.txt b/rustc-ice-2026-06-16T10_28_17-80379.txt new file mode 100644 index 00000000000..ced0e2dd7a5 --- /dev/null +++ b/rustc-ice-2026-06-16T10_28_17-80379.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10c69be20 - ::create + 1: 0x10a28b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10c6ade30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10c691dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10c6867c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10c6933d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c778c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c854fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10b9af0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10ba29744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10bbbfde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10c30d7e4 - ::evaluate_obligation + 12: 0x10c30dba0 - ::evaluate_obligation_no_overflow + 13: 0x10c2783a4 - ::process_trait_obligation + 14: 0x10c32bba4 - ::process_obligation + 15: 0x10c0b1c78 - >::process_obligations:: + 16: 0x10aa1d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a9597ac - ::check_argument_types + 18: 0x10a918234 - ::confirm_builtin_call + 19: 0x10a8ff854 - ::check_expr_kind + 20: 0x10a915010 - ::check_expr_with_expectation_and_args + 21: 0x10a959be0 - ::check_argument_types + 22: 0x10a8fd0c4 - ::check_expr_kind + 23: 0x10a915010 - ::check_expr_with_expectation_and_args + 24: 0x10a955b18 - ::check_expr_block + 25: 0x10a915010 - ::check_expr_with_expectation_and_args + 26: 0x10a912fd4 - ::check_return_or_body_tail + 27: 0x10a9effc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a8bc2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a8e5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10b9cc5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10bba72ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10a495154 - ::typeck:: + 33: 0x10a57090c - ::par_hir_body_owners::::{closure#0} + 34: 0x10a5ab84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10ac9f538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10b9efe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10bbde510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10a241f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10a28a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10a2816d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10a291ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10c6b8850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_31_12-83834.txt b/rustc-ice-2026-06-16T10_31_12-83834.txt new file mode 100644 index 00000000000..a3e660b5842 --- /dev/null +++ b/rustc-ice-2026-06-16T10_31_12-83834.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x109dc7e20 - ::create + 1: 0x1079b7690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x109dd9e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x109dbddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x109db27c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x109dbf3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x109ea4c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x109f80fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x1090db0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x109155744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x1092ebde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x109a397e4 - ::evaluate_obligation + 12: 0x109a39ba0 - ::evaluate_obligation_no_overflow + 13: 0x1099a43a4 - ::process_trait_obligation + 14: 0x109a57ba4 - ::process_obligation + 15: 0x1097ddc78 - >::process_obligations:: + 16: 0x1081496ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x1080857ac - ::check_argument_types + 18: 0x108044234 - ::confirm_builtin_call + 19: 0x10802b854 - ::check_expr_kind + 20: 0x108041010 - ::check_expr_with_expectation_and_args + 21: 0x108085be0 - ::check_argument_types + 22: 0x1080290c4 - ::check_expr_kind + 23: 0x108041010 - ::check_expr_with_expectation_and_args + 24: 0x108081b18 - ::check_expr_block + 25: 0x108041010 - ::check_expr_with_expectation_and_args + 26: 0x10803efd4 - ::check_return_or_body_tail + 27: 0x10811bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x107fe82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x108011b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x1090f85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x1092d32ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107bc1154 - ::typeck:: + 33: 0x107c9c90c - ::par_hir_body_owners::::{closure#0} + 34: 0x107cd784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x1083cb538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10911be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10930a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10796df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x1079b67d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1079ad6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x1079bdccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x109de4850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_31_12-83835.txt b/rustc-ice-2026-06-16T10_31_12-83835.txt new file mode 100644 index 00000000000..1cf6043e3cb --- /dev/null +++ b/rustc-ice-2026-06-16T10_31_12-83835.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10a10fe20 - ::create + 1: 0x107cff690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10a121e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10a105dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10a0fa7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10a1073d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10a1ecc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10a2c8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x1094230e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10949d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109633de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x109d817e4 - ::evaluate_obligation + 12: 0x109d81ba0 - ::evaluate_obligation_no_overflow + 13: 0x109cec3a4 - ::process_trait_obligation + 14: 0x109d9fba4 - ::process_obligation + 15: 0x109b25c78 - >::process_obligations:: + 16: 0x1084916ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x1083cd7ac - ::check_argument_types + 18: 0x10838c234 - ::confirm_builtin_call + 19: 0x108373854 - ::check_expr_kind + 20: 0x108389010 - ::check_expr_with_expectation_and_args + 21: 0x1083cdbe0 - ::check_argument_types + 22: 0x1083710c4 - ::check_expr_kind + 23: 0x108389010 - ::check_expr_with_expectation_and_args + 24: 0x1083c9b18 - ::check_expr_block + 25: 0x108389010 - ::check_expr_with_expectation_and_args + 26: 0x108386fd4 - ::check_return_or_body_tail + 27: 0x108463fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x1083302dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x108359b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x1094405c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10961b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x107f09154 - ::typeck:: + 33: 0x107fe490c - ::par_hir_body_owners::::{closure#0} + 34: 0x10801f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108713538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x109463e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109652510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x107cb5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x107cfe7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x107cf56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x107d05ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10a12c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_31_15-84148.txt b/rustc-ice-2026-06-16T10_31_15-84148.txt new file mode 100644 index 00000000000..883be8bafa6 --- /dev/null +++ b/rustc-ice-2026-06-16T10_31_15-84148.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10a54fe20 - ::create + 1: 0x10813f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10a561e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10a545dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10a53a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10a5473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10a62cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10a708fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x1098630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x1098dd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x109a73de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10a1c17e4 - ::evaluate_obligation + 12: 0x10a1c1ba0 - ::evaluate_obligation_no_overflow + 13: 0x10a12c3a4 - ::process_trait_obligation + 14: 0x10a1dfba4 - ::process_obligation + 15: 0x109f65c78 - >::process_obligations:: + 16: 0x1088d16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10880d7ac - ::check_argument_types + 18: 0x1087cc234 - ::confirm_builtin_call + 19: 0x1087b3854 - ::check_expr_kind + 20: 0x1087c9010 - ::check_expr_with_expectation_and_args + 21: 0x10880dbe0 - ::check_argument_types + 22: 0x1087b10c4 - ::check_expr_kind + 23: 0x1087c9010 - ::check_expr_with_expectation_and_args + 24: 0x108809b18 - ::check_expr_block + 25: 0x1087c9010 - ::check_expr_with_expectation_and_args + 26: 0x1087c6fd4 - ::check_return_or_body_tail + 27: 0x1088a3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x1087702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x108799b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x1098805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x109a5b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x108349154 - ::typeck:: + 33: 0x10842490c - ::par_hir_body_owners::::{closure#0} + 34: 0x10845f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x108b53538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x1098a3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x109a92510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x1080f5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10813e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1081356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x108145ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10a56c850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_31_15-84149.txt b/rustc-ice-2026-06-16T10_31_15-84149.txt new file mode 100644 index 00000000000..5563724de21 --- /dev/null +++ b/rustc-ice-2026-06-16T10_31_15-84149.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10be63e20 - ::create + 1: 0x109a53690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10be75e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10be59dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10be4e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10be5b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10bf40c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c01cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10b1770e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10b1f1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10b387de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10bad57e4 - ::evaluate_obligation + 12: 0x10bad5ba0 - ::evaluate_obligation_no_overflow + 13: 0x10ba403a4 - ::process_trait_obligation + 14: 0x10baf3ba4 - ::process_obligation + 15: 0x10b879c78 - >::process_obligations:: + 16: 0x10a1e56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a1217ac - ::check_argument_types + 18: 0x10a0e0234 - ::confirm_builtin_call + 19: 0x10a0c7854 - ::check_expr_kind + 20: 0x10a0dd010 - ::check_expr_with_expectation_and_args + 21: 0x10a121be0 - ::check_argument_types + 22: 0x10a0c50c4 - ::check_expr_kind + 23: 0x10a0dd010 - ::check_expr_with_expectation_and_args + 24: 0x10a11db18 - ::check_expr_block + 25: 0x10a0dd010 - ::check_expr_with_expectation_and_args + 26: 0x10a0dafd4 - ::check_return_or_body_tail + 27: 0x10a1b7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a0842dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a0adb44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10b1945c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10b36f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x109c5d154 - ::typeck:: + 33: 0x109d3890c - ::par_hir_body_owners::::{closure#0} + 34: 0x109d7384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10a467538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10b1b7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10b3a6510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x109a09f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x109a527d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x109a496d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x109a59ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10be80850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_31_35-84493.txt b/rustc-ice-2026-06-16T10_31_35-84493.txt new file mode 100644 index 00000000000..85667fc5df9 --- /dev/null +++ b/rustc-ice-2026-06-16T10_31_35-84493.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10b8d7e20 - ::create + 1: 0x1094c7690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10b8e9e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10b8cddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10b8c27c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10b8cf3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10b9b4c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10ba90fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10abeb0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10ac65744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10adfbde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10b5497e4 - ::evaluate_obligation + 12: 0x10b549ba0 - ::evaluate_obligation_no_overflow + 13: 0x10b4b43a4 - ::process_trait_obligation + 14: 0x10b567ba4 - ::process_obligation + 15: 0x10b2edc78 - >::process_obligations:: + 16: 0x109c596ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x109b957ac - ::check_argument_types + 18: 0x109b54234 - ::confirm_builtin_call + 19: 0x109b3b854 - ::check_expr_kind + 20: 0x109b51010 - ::check_expr_with_expectation_and_args + 21: 0x109b95be0 - ::check_argument_types + 22: 0x109b390c4 - ::check_expr_kind + 23: 0x109b51010 - ::check_expr_with_expectation_and_args + 24: 0x109b91b18 - ::check_expr_block + 25: 0x109b51010 - ::check_expr_with_expectation_and_args + 26: 0x109b4efd4 - ::check_return_or_body_tail + 27: 0x109c2bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x109af82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x109b21b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10ac085c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10ade32ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x1096d1154 - ::typeck:: + 33: 0x1097ac90c - ::par_hir_body_owners::::{closure#0} + 34: 0x1097e784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x109edb538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10ac2be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10ae1a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10947df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x1094c67d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x1094bd6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x1094cdccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10b8f4850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_31_35-84494.txt b/rustc-ice-2026-06-16T10_31_35-84494.txt new file mode 100644 index 00000000000..d97645d104d --- /dev/null +++ b/rustc-ice-2026-06-16T10_31_35-84494.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10c05be20 - ::create + 1: 0x109c4b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10c06de30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10c051dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10c0467c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10c0533d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c138c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c214fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10b36f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10b3e9744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10b57fde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10bccd7e4 - ::evaluate_obligation + 12: 0x10bccdba0 - ::evaluate_obligation_no_overflow + 13: 0x10bc383a4 - ::process_trait_obligation + 14: 0x10bcebba4 - ::process_obligation + 15: 0x10ba71c78 - >::process_obligations:: + 16: 0x10a3dd6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a3197ac - ::check_argument_types + 18: 0x10a2d8234 - ::confirm_builtin_call + 19: 0x10a2bf854 - ::check_expr_kind + 20: 0x10a2d5010 - ::check_expr_with_expectation_and_args + 21: 0x10a319be0 - ::check_argument_types + 22: 0x10a2bd0c4 - ::check_expr_kind + 23: 0x10a2d5010 - ::check_expr_with_expectation_and_args + 24: 0x10a315b18 - ::check_expr_block + 25: 0x10a2d5010 - ::check_expr_with_expectation_and_args + 26: 0x10a2d2fd4 - ::check_return_or_body_tail + 27: 0x10a3affc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a27c2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a2a5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10b38c5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10b5672ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x109e55154 - ::typeck:: + 33: 0x109f3090c - ::par_hir_body_owners::::{closure#0} + 34: 0x109f6b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10a65f538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10b3afe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10b59e510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x109c01f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x109c4a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x109c416d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x109c51ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10c078850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_32_02-84897.txt b/rustc-ice-2026-06-16T10_32_02-84897.txt new file mode 100644 index 00000000000..df5ac3614d5 --- /dev/null +++ b/rustc-ice-2026-06-16T10_32_02-84897.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10e553e20 - ::create + 1: 0x10c143690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10e565e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10e549dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10e53e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10e54b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10e630c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10e70cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10d8670e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10d8e1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10da77de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10e1c57e4 - ::evaluate_obligation + 12: 0x10e1c5ba0 - ::evaluate_obligation_no_overflow + 13: 0x10e1303a4 - ::process_trait_obligation + 14: 0x10e1e3ba4 - ::process_obligation + 15: 0x10df69c78 - >::process_obligations:: + 16: 0x10c8d56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10c8117ac - ::check_argument_types + 18: 0x10c7d0234 - ::confirm_builtin_call + 19: 0x10c7b7854 - ::check_expr_kind + 20: 0x10c7cd010 - ::check_expr_with_expectation_and_args + 21: 0x10c811be0 - ::check_argument_types + 22: 0x10c7b50c4 - ::check_expr_kind + 23: 0x10c7cd010 - ::check_expr_with_expectation_and_args + 24: 0x10c80db18 - ::check_expr_block + 25: 0x10c7cd010 - ::check_expr_with_expectation_and_args + 26: 0x10c7cafd4 - ::check_return_or_body_tail + 27: 0x10c8a7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10c7742dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10c79db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10d8845c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10da5f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10c34d154 - ::typeck:: + 33: 0x10c42890c - ::par_hir_body_owners::::{closure#0} + 34: 0x10c46384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10cb57538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10d8a7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10da96510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10c0f9f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10c1427d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10c1396d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10c149ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10e570850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_32_02-84898.txt b/rustc-ice-2026-06-16T10_32_02-84898.txt new file mode 100644 index 00000000000..2ed4e73be16 --- /dev/null +++ b/rustc-ice-2026-06-16T10_32_02-84898.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10ddebe20 - ::create + 1: 0x10b9db690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10ddfde30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10dde1dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10ddd67c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10dde33d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10dec8c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10dfa4fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10d0ff0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10d179744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10d30fde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10da5d7e4 - ::evaluate_obligation + 12: 0x10da5dba0 - ::evaluate_obligation_no_overflow + 13: 0x10d9c83a4 - ::process_trait_obligation + 14: 0x10da7bba4 - ::process_obligation + 15: 0x10d801c78 - >::process_obligations:: + 16: 0x10c16d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10c0a97ac - ::check_argument_types + 18: 0x10c068234 - ::confirm_builtin_call + 19: 0x10c04f854 - ::check_expr_kind + 20: 0x10c065010 - ::check_expr_with_expectation_and_args + 21: 0x10c0a9be0 - ::check_argument_types + 22: 0x10c04d0c4 - ::check_expr_kind + 23: 0x10c065010 - ::check_expr_with_expectation_and_args + 24: 0x10c0a5b18 - ::check_expr_block + 25: 0x10c065010 - ::check_expr_with_expectation_and_args + 26: 0x10c062fd4 - ::check_return_or_body_tail + 27: 0x10c13ffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10c00c2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10c035b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10d11c5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10d2f72ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10bbe5154 - ::typeck:: + 33: 0x10bcc090c - ::par_hir_body_owners::::{closure#0} + 34: 0x10bcfb84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10c3ef538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10d13fe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10d32e510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10b991f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10b9da7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10b9d16d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10b9e1ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10de08850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_32_06-85213.txt b/rustc-ice-2026-06-16T10_32_06-85213.txt new file mode 100644 index 00000000000..374510d8c28 --- /dev/null +++ b/rustc-ice-2026-06-16T10_32_06-85213.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10dc33e20 - ::create + 1: 0x10b823690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10dc45e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10dc29dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10dc1e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10dc2b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10dd10c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10ddecfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10cf470e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10cfc1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10d157de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10d8a57e4 - ::evaluate_obligation + 12: 0x10d8a5ba0 - ::evaluate_obligation_no_overflow + 13: 0x10d8103a4 - ::process_trait_obligation + 14: 0x10d8c3ba4 - ::process_obligation + 15: 0x10d649c78 - >::process_obligations:: + 16: 0x10bfb56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10bef17ac - ::check_argument_types + 18: 0x10beb0234 - ::confirm_builtin_call + 19: 0x10be97854 - ::check_expr_kind + 20: 0x10bead010 - ::check_expr_with_expectation_and_args + 21: 0x10bef1be0 - ::check_argument_types + 22: 0x10be950c4 - ::check_expr_kind + 23: 0x10bead010 - ::check_expr_with_expectation_and_args + 24: 0x10beedb18 - ::check_expr_block + 25: 0x10bead010 - ::check_expr_with_expectation_and_args + 26: 0x10beaafd4 - ::check_return_or_body_tail + 27: 0x10bf87fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10be542dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10be7db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10cf645c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10d13f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10ba2d154 - ::typeck:: + 33: 0x10bb0890c - ::par_hir_body_owners::::{closure#0} + 34: 0x10bb4384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10c237538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10cf87e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10d176510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10b7d9f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10b8227d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10b8196d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10b829ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10dc50850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack diff --git a/rustc-ice-2026-06-16T10_32_06-85214.txt b/rustc-ice-2026-06-16T10_32_06-85214.txt new file mode 100644 index 00000000000..aa57c4141c8 --- /dev/null +++ b/rustc-ice-2026-06-16T10_32_06-85214.txt @@ -0,0 +1,57 @@ +thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: +Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) +stack backtrace: + 0: 0x10c4c7e20 - ::create + 1: 0x10a0b7690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} + 2: 0x10c4d9e30 - std[4eea21f33c263573]::panicking::panic_with_hook + 3: 0x10c4bddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} + 4: 0x10c4b27c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: + 5: 0x10c4bf3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind + 6: 0x10c5a4c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt + 7: 0x10c680fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed + 8: 0x10b7db0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> + 9: 0x10b855744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> + 10: 0x10b9ebde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace + 11: 0x10c1397e4 - ::evaluate_obligation + 12: 0x10c139ba0 - ::evaluate_obligation_no_overflow + 13: 0x10c0a43a4 - ::process_trait_obligation + 14: 0x10c157ba4 - ::process_obligation + 15: 0x10beddc78 - >::process_obligations:: + 16: 0x10a8496ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations + 17: 0x10a7857ac - ::check_argument_types + 18: 0x10a744234 - ::confirm_builtin_call + 19: 0x10a72b854 - ::check_expr_kind + 20: 0x10a741010 - ::check_expr_with_expectation_and_args + 21: 0x10a785be0 - ::check_argument_types + 22: 0x10a7290c4 - ::check_expr_kind + 23: 0x10a741010 - ::check_expr_with_expectation_and_args + 24: 0x10a781b18 - ::check_expr_block + 25: 0x10a741010 - ::check_expr_with_expectation_and_args + 26: 0x10a73efd4 - ::check_return_or_body_tail + 27: 0x10a81bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn + 28: 0x10a6e82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} + 29: 0x10a711b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root + 30: 0x10b7f85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> + 31: 0x10b9d32ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace + 32: 0x10a2c1154 - ::typeck:: + 33: 0x10a39c90c - ::par_hir_body_owners::::{closure#0} + 34: 0x10a3d784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate + 35: 0x10aacb538 - rustc_interface[12658516f1d6a68f]::passes::analysis + 36: 0x10b81be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> + 37: 0x10ba0a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace + 38: 0x10a06df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> + 39: 0x10a0b67d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} + 40: 0x10a0ad6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> + 41: 0x10a0bdccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} + 42: 0x10c4e4850 - ::new::thread_start + 43: 0x18995fc58 - __pthread_cond_wait + + +rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) +platform: aarch64-apple-darwin + +query stack during panic: +#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` +#1 [typeck_root] type-checking `rest::hashql::::routes` +#2 [analysis] running analysis passes on crate `hash_graph_api` +end of query stack From fcd340f725111c967f319036169c224ee0f1e454 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:52:34 +0200 Subject: [PATCH 04/34] chore: remove ice ice baby --- rustc-ice-2026-06-16T10_26_13-75957.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_13-75958.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_17-76258.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_17-76259.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_22-76588.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_22-76589.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_29-76928.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_29-76929.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_33-77252.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_33-77253.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_58-77896.txt | 57 ------------------------- rustc-ice-2026-06-16T10_26_58-77897.txt | 57 ------------------------- rustc-ice-2026-06-16T10_27_05-78213.txt | 57 ------------------------- rustc-ice-2026-06-16T10_27_05-78214.txt | 57 ------------------------- rustc-ice-2026-06-16T10_27_22-78527.txt | 57 ------------------------- rustc-ice-2026-06-16T10_27_22-78528.txt | 57 ------------------------- rustc-ice-2026-06-16T10_28_07-79736.txt | 57 ------------------------- rustc-ice-2026-06-16T10_28_07-79737.txt | 57 ------------------------- rustc-ice-2026-06-16T10_28_17-80378.txt | 57 ------------------------- rustc-ice-2026-06-16T10_28_17-80379.txt | 57 ------------------------- rustc-ice-2026-06-16T10_31_12-83834.txt | 57 ------------------------- rustc-ice-2026-06-16T10_31_12-83835.txt | 57 ------------------------- rustc-ice-2026-06-16T10_31_15-84148.txt | 57 ------------------------- rustc-ice-2026-06-16T10_31_15-84149.txt | 57 ------------------------- rustc-ice-2026-06-16T10_31_35-84493.txt | 57 ------------------------- rustc-ice-2026-06-16T10_31_35-84494.txt | 57 ------------------------- rustc-ice-2026-06-16T10_32_02-84897.txt | 57 ------------------------- rustc-ice-2026-06-16T10_32_02-84898.txt | 57 ------------------------- rustc-ice-2026-06-16T10_32_06-85213.txt | 57 ------------------------- rustc-ice-2026-06-16T10_32_06-85214.txt | 57 ------------------------- 30 files changed, 1710 deletions(-) delete mode 100644 rustc-ice-2026-06-16T10_26_13-75957.txt delete mode 100644 rustc-ice-2026-06-16T10_26_13-75958.txt delete mode 100644 rustc-ice-2026-06-16T10_26_17-76258.txt delete mode 100644 rustc-ice-2026-06-16T10_26_17-76259.txt delete mode 100644 rustc-ice-2026-06-16T10_26_22-76588.txt delete mode 100644 rustc-ice-2026-06-16T10_26_22-76589.txt delete mode 100644 rustc-ice-2026-06-16T10_26_29-76928.txt delete mode 100644 rustc-ice-2026-06-16T10_26_29-76929.txt delete mode 100644 rustc-ice-2026-06-16T10_26_33-77252.txt delete mode 100644 rustc-ice-2026-06-16T10_26_33-77253.txt delete mode 100644 rustc-ice-2026-06-16T10_26_58-77896.txt delete mode 100644 rustc-ice-2026-06-16T10_26_58-77897.txt delete mode 100644 rustc-ice-2026-06-16T10_27_05-78213.txt delete mode 100644 rustc-ice-2026-06-16T10_27_05-78214.txt delete mode 100644 rustc-ice-2026-06-16T10_27_22-78527.txt delete mode 100644 rustc-ice-2026-06-16T10_27_22-78528.txt delete mode 100644 rustc-ice-2026-06-16T10_28_07-79736.txt delete mode 100644 rustc-ice-2026-06-16T10_28_07-79737.txt delete mode 100644 rustc-ice-2026-06-16T10_28_17-80378.txt delete mode 100644 rustc-ice-2026-06-16T10_28_17-80379.txt delete mode 100644 rustc-ice-2026-06-16T10_31_12-83834.txt delete mode 100644 rustc-ice-2026-06-16T10_31_12-83835.txt delete mode 100644 rustc-ice-2026-06-16T10_31_15-84148.txt delete mode 100644 rustc-ice-2026-06-16T10_31_15-84149.txt delete mode 100644 rustc-ice-2026-06-16T10_31_35-84493.txt delete mode 100644 rustc-ice-2026-06-16T10_31_35-84494.txt delete mode 100644 rustc-ice-2026-06-16T10_32_02-84897.txt delete mode 100644 rustc-ice-2026-06-16T10_32_02-84898.txt delete mode 100644 rustc-ice-2026-06-16T10_32_06-85213.txt delete mode 100644 rustc-ice-2026-06-16T10_32_06-85214.txt diff --git a/rustc-ice-2026-06-16T10_26_13-75957.txt b/rustc-ice-2026-06-16T10_26_13-75957.txt deleted file mode 100644 index b2918f4c4cd..00000000000 --- a/rustc-ice-2026-06-16T10_26_13-75957.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10a6e3e20 - ::create - 1: 0x1082d3690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10a6f5e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10a6d9dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10a6ce7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10a6db3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10a7c0c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10a89cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x1099f70e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x109a71744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109c07de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10a3557e4 - ::evaluate_obligation - 12: 0x10a355ba0 - ::evaluate_obligation_no_overflow - 13: 0x10a2c03a4 - ::process_trait_obligation - 14: 0x10a373ba4 - ::process_obligation - 15: 0x10a0f9c78 - >::process_obligations:: - 16: 0x108a656ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x1089a17ac - ::check_argument_types - 18: 0x108960234 - ::confirm_builtin_call - 19: 0x108947854 - ::check_expr_kind - 20: 0x10895d010 - ::check_expr_with_expectation_and_args - 21: 0x1089a1be0 - ::check_argument_types - 22: 0x1089450c4 - ::check_expr_kind - 23: 0x10895d010 - ::check_expr_with_expectation_and_args - 24: 0x10899db18 - ::check_expr_block - 25: 0x10895d010 - ::check_expr_with_expectation_and_args - 26: 0x10895afd4 - ::check_return_or_body_tail - 27: 0x108a37fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x1089042dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10892db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x109a145c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x109bef2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x1084dd154 - ::typeck:: - 33: 0x1085b890c - ::par_hir_body_owners::::{closure#0} - 34: 0x1085f384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108ce7538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x109a37e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109c26510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x108289f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x1082d27d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1082c96d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x1082d9ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10a700850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_13-75958.txt b/rustc-ice-2026-06-16T10_26_13-75958.txt deleted file mode 100644 index 324dde9c512..00000000000 --- a/rustc-ice-2026-06-16T10_26_13-75958.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x109e2fe20 - ::create - 1: 0x107a1f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x109e41e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x109e25dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x109e1a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x109e273d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x109f0cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x109fe8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x1091430e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x1091bd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109353de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x109aa17e4 - ::evaluate_obligation - 12: 0x109aa1ba0 - ::evaluate_obligation_no_overflow - 13: 0x109a0c3a4 - ::process_trait_obligation - 14: 0x109abfba4 - ::process_obligation - 15: 0x109845c78 - >::process_obligations:: - 16: 0x1081b16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x1080ed7ac - ::check_argument_types - 18: 0x1080ac234 - ::confirm_builtin_call - 19: 0x108093854 - ::check_expr_kind - 20: 0x1080a9010 - ::check_expr_with_expectation_and_args - 21: 0x1080edbe0 - ::check_argument_types - 22: 0x1080910c4 - ::check_expr_kind - 23: 0x1080a9010 - ::check_expr_with_expectation_and_args - 24: 0x1080e9b18 - ::check_expr_block - 25: 0x1080a9010 - ::check_expr_with_expectation_and_args - 26: 0x1080a6fd4 - ::check_return_or_body_tail - 27: 0x108183fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x1080502dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x108079b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x1091605c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10933b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107c29154 - ::typeck:: - 33: 0x107d0490c - ::par_hir_body_owners::::{closure#0} - 34: 0x107d3f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108433538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x109183e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109372510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x1079d5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x107a1e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x107a156d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x107a25ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x109e4c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_17-76258.txt b/rustc-ice-2026-06-16T10_26_17-76258.txt deleted file mode 100644 index 47c7803bf36..00000000000 --- a/rustc-ice-2026-06-16T10_26_17-76258.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10b84fe20 - ::create - 1: 0x10943f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10b861e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10b845dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10b83a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10b8473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10b92cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10ba08fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10ab630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10abdd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10ad73de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10b4c17e4 - ::evaluate_obligation - 12: 0x10b4c1ba0 - ::evaluate_obligation_no_overflow - 13: 0x10b42c3a4 - ::process_trait_obligation - 14: 0x10b4dfba4 - ::process_obligation - 15: 0x10b265c78 - >::process_obligations:: - 16: 0x109bd16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x109b0d7ac - ::check_argument_types - 18: 0x109acc234 - ::confirm_builtin_call - 19: 0x109ab3854 - ::check_expr_kind - 20: 0x109ac9010 - ::check_expr_with_expectation_and_args - 21: 0x109b0dbe0 - ::check_argument_types - 22: 0x109ab10c4 - ::check_expr_kind - 23: 0x109ac9010 - ::check_expr_with_expectation_and_args - 24: 0x109b09b18 - ::check_expr_block - 25: 0x109ac9010 - ::check_expr_with_expectation_and_args - 26: 0x109ac6fd4 - ::check_return_or_body_tail - 27: 0x109ba3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x109a702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x109a99b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10ab805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10ad5b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x109649154 - ::typeck:: - 33: 0x10972490c - ::par_hir_body_owners::::{closure#0} - 34: 0x10975f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x109e53538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10aba3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10ad92510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x1093f5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10943e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1094356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x109445ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10b86c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_17-76259.txt b/rustc-ice-2026-06-16T10_26_17-76259.txt deleted file mode 100644 index 5c24d26c359..00000000000 --- a/rustc-ice-2026-06-16T10_26_17-76259.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10c04fe20 - ::create - 1: 0x109c3f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10c061e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10c045dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10c03a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10c0473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c12cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c208fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10b3630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10b3dd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10b573de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10bcc17e4 - ::evaluate_obligation - 12: 0x10bcc1ba0 - ::evaluate_obligation_no_overflow - 13: 0x10bc2c3a4 - ::process_trait_obligation - 14: 0x10bcdfba4 - ::process_obligation - 15: 0x10ba65c78 - >::process_obligations:: - 16: 0x10a3d16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a30d7ac - ::check_argument_types - 18: 0x10a2cc234 - ::confirm_builtin_call - 19: 0x10a2b3854 - ::check_expr_kind - 20: 0x10a2c9010 - ::check_expr_with_expectation_and_args - 21: 0x10a30dbe0 - ::check_argument_types - 22: 0x10a2b10c4 - ::check_expr_kind - 23: 0x10a2c9010 - ::check_expr_with_expectation_and_args - 24: 0x10a309b18 - ::check_expr_block - 25: 0x10a2c9010 - ::check_expr_with_expectation_and_args - 26: 0x10a2c6fd4 - ::check_return_or_body_tail - 27: 0x10a3a3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a2702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a299b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10b3805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10b55b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x109e49154 - ::typeck:: - 33: 0x109f2490c - ::par_hir_body_owners::::{closure#0} - 34: 0x109f5f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10a653538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10b3a3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10b592510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x109bf5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x109c3e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x109c356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x109c45ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10c06c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_22-76588.txt b/rustc-ice-2026-06-16T10_26_22-76588.txt deleted file mode 100644 index 6e61a0bbdde..00000000000 --- a/rustc-ice-2026-06-16T10_26_22-76588.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x109d4fe20 - ::create - 1: 0x10793f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x109d61e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x109d45dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x109d3a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x109d473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x109e2cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x109f08fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x1090630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x1090dd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109273de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x1099c17e4 - ::evaluate_obligation - 12: 0x1099c1ba0 - ::evaluate_obligation_no_overflow - 13: 0x10992c3a4 - ::process_trait_obligation - 14: 0x1099dfba4 - ::process_obligation - 15: 0x109765c78 - >::process_obligations:: - 16: 0x1080d16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10800d7ac - ::check_argument_types - 18: 0x107fcc234 - ::confirm_builtin_call - 19: 0x107fb3854 - ::check_expr_kind - 20: 0x107fc9010 - ::check_expr_with_expectation_and_args - 21: 0x10800dbe0 - ::check_argument_types - 22: 0x107fb10c4 - ::check_expr_kind - 23: 0x107fc9010 - ::check_expr_with_expectation_and_args - 24: 0x108009b18 - ::check_expr_block - 25: 0x107fc9010 - ::check_expr_with_expectation_and_args - 26: 0x107fc6fd4 - ::check_return_or_body_tail - 27: 0x1080a3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x107f702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x107f99b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x1090805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10925b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107b49154 - ::typeck:: - 33: 0x107c2490c - ::par_hir_body_owners::::{closure#0} - 34: 0x107c5f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108353538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x1090a3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109292510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x1078f5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10793e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1079356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x107945ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x109d6c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_22-76589.txt b/rustc-ice-2026-06-16T10_26_22-76589.txt deleted file mode 100644 index 0e64ba1920e..00000000000 --- a/rustc-ice-2026-06-16T10_26_22-76589.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10c587e20 - ::create - 1: 0x10a177690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10c599e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10c57ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10c5727c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10c57f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c664c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c740fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10b89b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10b915744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10baabde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10c1f97e4 - ::evaluate_obligation - 12: 0x10c1f9ba0 - ::evaluate_obligation_no_overflow - 13: 0x10c1643a4 - ::process_trait_obligation - 14: 0x10c217ba4 - ::process_obligation - 15: 0x10bf9dc78 - >::process_obligations:: - 16: 0x10a9096ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a8457ac - ::check_argument_types - 18: 0x10a804234 - ::confirm_builtin_call - 19: 0x10a7eb854 - ::check_expr_kind - 20: 0x10a801010 - ::check_expr_with_expectation_and_args - 21: 0x10a845be0 - ::check_argument_types - 22: 0x10a7e90c4 - ::check_expr_kind - 23: 0x10a801010 - ::check_expr_with_expectation_and_args - 24: 0x10a841b18 - ::check_expr_block - 25: 0x10a801010 - ::check_expr_with_expectation_and_args - 26: 0x10a7fefd4 - ::check_return_or_body_tail - 27: 0x10a8dbfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a7a82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a7d1b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10b8b85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10ba932ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10a381154 - ::typeck:: - 33: 0x10a45c90c - ::par_hir_body_owners::::{closure#0} - 34: 0x10a49784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10ab8b538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10b8dbe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10baca510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10a12df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10a1767d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10a16d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10a17dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10c5a4850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_29-76928.txt b/rustc-ice-2026-06-16T10_26_29-76928.txt deleted file mode 100644 index 2a43c7ffa46..00000000000 --- a/rustc-ice-2026-06-16T10_26_29-76928.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10990fe20 - ::create - 1: 0x1074ff690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x109921e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x109905dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x1098fa7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x1099073d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x1099ecc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x109ac8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x108c230e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x108c9d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x108e33de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x1095817e4 - ::evaluate_obligation - 12: 0x109581ba0 - ::evaluate_obligation_no_overflow - 13: 0x1094ec3a4 - ::process_trait_obligation - 14: 0x10959fba4 - ::process_obligation - 15: 0x109325c78 - >::process_obligations:: - 16: 0x107c916ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x107bcd7ac - ::check_argument_types - 18: 0x107b8c234 - ::confirm_builtin_call - 19: 0x107b73854 - ::check_expr_kind - 20: 0x107b89010 - ::check_expr_with_expectation_and_args - 21: 0x107bcdbe0 - ::check_argument_types - 22: 0x107b710c4 - ::check_expr_kind - 23: 0x107b89010 - ::check_expr_with_expectation_and_args - 24: 0x107bc9b18 - ::check_expr_block - 25: 0x107b89010 - ::check_expr_with_expectation_and_args - 26: 0x107b86fd4 - ::check_return_or_body_tail - 27: 0x107c63fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x107b302dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x107b59b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x108c405c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x108e1b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107709154 - ::typeck:: - 33: 0x1077e490c - ::par_hir_body_owners::::{closure#0} - 34: 0x10781f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x107f13538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x108c63e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x108e52510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x1074b5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x1074fe7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1074f56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x107505ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10992c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_29-76929.txt b/rustc-ice-2026-06-16T10_26_29-76929.txt deleted file mode 100644 index d4d6d96d9b7..00000000000 --- a/rustc-ice-2026-06-16T10_26_29-76929.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x109b93e20 - ::create - 1: 0x107783690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x109ba5e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x109b89dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x109b7e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x109b8b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x109c70c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x109d4cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x108ea70e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x108f21744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x1090b7de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x1098057e4 - ::evaluate_obligation - 12: 0x109805ba0 - ::evaluate_obligation_no_overflow - 13: 0x1097703a4 - ::process_trait_obligation - 14: 0x109823ba4 - ::process_obligation - 15: 0x1095a9c78 - >::process_obligations:: - 16: 0x107f156ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x107e517ac - ::check_argument_types - 18: 0x107e10234 - ::confirm_builtin_call - 19: 0x107df7854 - ::check_expr_kind - 20: 0x107e0d010 - ::check_expr_with_expectation_and_args - 21: 0x107e51be0 - ::check_argument_types - 22: 0x107df50c4 - ::check_expr_kind - 23: 0x107e0d010 - ::check_expr_with_expectation_and_args - 24: 0x107e4db18 - ::check_expr_block - 25: 0x107e0d010 - ::check_expr_with_expectation_and_args - 26: 0x107e0afd4 - ::check_return_or_body_tail - 27: 0x107ee7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x107db42dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x107dddb44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x108ec45c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10909f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10798d154 - ::typeck:: - 33: 0x107a6890c - ::par_hir_body_owners::::{closure#0} - 34: 0x107aa384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108197538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x108ee7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x1090d6510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x107739f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x1077827d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1077796d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x107789ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x109bb0850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_33-77252.txt b/rustc-ice-2026-06-16T10_26_33-77252.txt deleted file mode 100644 index e7689de5979..00000000000 --- a/rustc-ice-2026-06-16T10_26_33-77252.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10a58be20 - ::create - 1: 0x10817b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10a59de30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10a581dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10a5767c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10a5833d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10a668c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10a744fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10989f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x109919744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109aafde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10a1fd7e4 - ::evaluate_obligation - 12: 0x10a1fdba0 - ::evaluate_obligation_no_overflow - 13: 0x10a1683a4 - ::process_trait_obligation - 14: 0x10a21bba4 - ::process_obligation - 15: 0x109fa1c78 - >::process_obligations:: - 16: 0x10890d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x1088497ac - ::check_argument_types - 18: 0x108808234 - ::confirm_builtin_call - 19: 0x1087ef854 - ::check_expr_kind - 20: 0x108805010 - ::check_expr_with_expectation_and_args - 21: 0x108849be0 - ::check_argument_types - 22: 0x1087ed0c4 - ::check_expr_kind - 23: 0x108805010 - ::check_expr_with_expectation_and_args - 24: 0x108845b18 - ::check_expr_block - 25: 0x108805010 - ::check_expr_with_expectation_and_args - 26: 0x108802fd4 - ::check_return_or_body_tail - 27: 0x1088dffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x1087ac2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x1087d5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x1098bc5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x109a972ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x108385154 - ::typeck:: - 33: 0x10846090c - ::par_hir_body_owners::::{closure#0} - 34: 0x10849b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108b8f538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x1098dfe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109ace510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x108131f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10817a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1081716d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x108181ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10a5a8850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_33-77253.txt b/rustc-ice-2026-06-16T10_26_33-77253.txt deleted file mode 100644 index 88dbb6f5d1e..00000000000 --- a/rustc-ice-2026-06-16T10_26_33-77253.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10c857e20 - ::create - 1: 0x10a447690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10c869e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10c84ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10c8427c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10c84f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c934c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10ca10fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10bb6b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10bbe5744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10bd7bde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10c4c97e4 - ::evaluate_obligation - 12: 0x10c4c9ba0 - ::evaluate_obligation_no_overflow - 13: 0x10c4343a4 - ::process_trait_obligation - 14: 0x10c4e7ba4 - ::process_obligation - 15: 0x10c26dc78 - >::process_obligations:: - 16: 0x10abd96ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10ab157ac - ::check_argument_types - 18: 0x10aad4234 - ::confirm_builtin_call - 19: 0x10aabb854 - ::check_expr_kind - 20: 0x10aad1010 - ::check_expr_with_expectation_and_args - 21: 0x10ab15be0 - ::check_argument_types - 22: 0x10aab90c4 - ::check_expr_kind - 23: 0x10aad1010 - ::check_expr_with_expectation_and_args - 24: 0x10ab11b18 - ::check_expr_block - 25: 0x10aad1010 - ::check_expr_with_expectation_and_args - 26: 0x10aacefd4 - ::check_return_or_body_tail - 27: 0x10ababfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10aa782dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10aaa1b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10bb885c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10bd632ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10a651154 - ::typeck:: - 33: 0x10a72c90c - ::par_hir_body_owners::::{closure#0} - 34: 0x10a76784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10ae5b538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10bbabe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10bd9a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10a3fdf8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10a4467d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10a43d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10a44dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10c874850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_58-77896.txt b/rustc-ice-2026-06-16T10_26_58-77896.txt deleted file mode 100644 index 7091332b816..00000000000 --- a/rustc-ice-2026-06-16T10_26_58-77896.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10c727e20 - ::create - 1: 0x10a317690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10c739e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10c71ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10c7127c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10c71f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c804c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c8e0fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10ba3b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10bab5744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10bc4bde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10c3997e4 - ::evaluate_obligation - 12: 0x10c399ba0 - ::evaluate_obligation_no_overflow - 13: 0x10c3043a4 - ::process_trait_obligation - 14: 0x10c3b7ba4 - ::process_obligation - 15: 0x10c13dc78 - >::process_obligations:: - 16: 0x10aaa96ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a9e57ac - ::check_argument_types - 18: 0x10a9a4234 - ::confirm_builtin_call - 19: 0x10a98b854 - ::check_expr_kind - 20: 0x10a9a1010 - ::check_expr_with_expectation_and_args - 21: 0x10a9e5be0 - ::check_argument_types - 22: 0x10a9890c4 - ::check_expr_kind - 23: 0x10a9a1010 - ::check_expr_with_expectation_and_args - 24: 0x10a9e1b18 - ::check_expr_block - 25: 0x10a9a1010 - ::check_expr_with_expectation_and_args - 26: 0x10a99efd4 - ::check_return_or_body_tail - 27: 0x10aa7bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a9482dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a971b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10ba585c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10bc332ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10a521154 - ::typeck:: - 33: 0x10a5fc90c - ::par_hir_body_owners::::{closure#0} - 34: 0x10a63784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10ad2b538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10ba7be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10bc6a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10a2cdf8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10a3167d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10a30d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10a31dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10c744850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_26_58-77897.txt b/rustc-ice-2026-06-16T10_26_58-77897.txt deleted file mode 100644 index 5a68d120006..00000000000 --- a/rustc-ice-2026-06-16T10_26_58-77897.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10a01fe20 - ::create - 1: 0x107c0f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10a031e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10a015dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10a00a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10a0173d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10a0fcc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10a1d8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x1093330e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x1093ad744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109543de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x109c917e4 - ::evaluate_obligation - 12: 0x109c91ba0 - ::evaluate_obligation_no_overflow - 13: 0x109bfc3a4 - ::process_trait_obligation - 14: 0x109cafba4 - ::process_obligation - 15: 0x109a35c78 - >::process_obligations:: - 16: 0x1083a16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x1082dd7ac - ::check_argument_types - 18: 0x10829c234 - ::confirm_builtin_call - 19: 0x108283854 - ::check_expr_kind - 20: 0x108299010 - ::check_expr_with_expectation_and_args - 21: 0x1082ddbe0 - ::check_argument_types - 22: 0x1082810c4 - ::check_expr_kind - 23: 0x108299010 - ::check_expr_with_expectation_and_args - 24: 0x1082d9b18 - ::check_expr_block - 25: 0x108299010 - ::check_expr_with_expectation_and_args - 26: 0x108296fd4 - ::check_return_or_body_tail - 27: 0x108373fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x1082402dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x108269b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x1093505c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10952b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107e19154 - ::typeck:: - 33: 0x107ef490c - ::par_hir_body_owners::::{closure#0} - 34: 0x107f2f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108623538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x109373e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109562510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x107bc5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x107c0e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x107c056d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x107c15ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10a03c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_27_05-78213.txt b/rustc-ice-2026-06-16T10_27_05-78213.txt deleted file mode 100644 index b5b0501ab36..00000000000 --- a/rustc-ice-2026-06-16T10_27_05-78213.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x109b77e20 - ::create - 1: 0x107767690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x109b89e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x109b6ddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x109b627c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x109b6f3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x109c54c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x109d30fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x108e8b0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x108f05744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10909bde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x1097e97e4 - ::evaluate_obligation - 12: 0x1097e9ba0 - ::evaluate_obligation_no_overflow - 13: 0x1097543a4 - ::process_trait_obligation - 14: 0x109807ba4 - ::process_obligation - 15: 0x10958dc78 - >::process_obligations:: - 16: 0x107ef96ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x107e357ac - ::check_argument_types - 18: 0x107df4234 - ::confirm_builtin_call - 19: 0x107ddb854 - ::check_expr_kind - 20: 0x107df1010 - ::check_expr_with_expectation_and_args - 21: 0x107e35be0 - ::check_argument_types - 22: 0x107dd90c4 - ::check_expr_kind - 23: 0x107df1010 - ::check_expr_with_expectation_and_args - 24: 0x107e31b18 - ::check_expr_block - 25: 0x107df1010 - ::check_expr_with_expectation_and_args - 26: 0x107deefd4 - ::check_return_or_body_tail - 27: 0x107ecbfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x107d982dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x107dc1b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x108ea85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x1090832ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107971154 - ::typeck:: - 33: 0x107a4c90c - ::par_hir_body_owners::::{closure#0} - 34: 0x107a8784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10817b538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x108ecbe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x1090ba510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10771df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x1077667d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10775d6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10776dccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x109b94850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_27_05-78214.txt b/rustc-ice-2026-06-16T10_27_05-78214.txt deleted file mode 100644 index 6d78247e121..00000000000 --- a/rustc-ice-2026-06-16T10_27_05-78214.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x109c8be20 - ::create - 1: 0x10787b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x109c9de30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x109c81dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x109c767c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x109c833d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x109d68c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x109e44fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x108f9f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x109019744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x1091afde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x1098fd7e4 - ::evaluate_obligation - 12: 0x1098fdba0 - ::evaluate_obligation_no_overflow - 13: 0x1098683a4 - ::process_trait_obligation - 14: 0x10991bba4 - ::process_obligation - 15: 0x1096a1c78 - >::process_obligations:: - 16: 0x10800d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x107f497ac - ::check_argument_types - 18: 0x107f08234 - ::confirm_builtin_call - 19: 0x107eef854 - ::check_expr_kind - 20: 0x107f05010 - ::check_expr_with_expectation_and_args - 21: 0x107f49be0 - ::check_argument_types - 22: 0x107eed0c4 - ::check_expr_kind - 23: 0x107f05010 - ::check_expr_with_expectation_and_args - 24: 0x107f45b18 - ::check_expr_block - 25: 0x107f05010 - ::check_expr_with_expectation_and_args - 26: 0x107f02fd4 - ::check_return_or_body_tail - 27: 0x107fdffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x107eac2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x107ed5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x108fbc5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x1091972ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107a85154 - ::typeck:: - 33: 0x107b6090c - ::par_hir_body_owners::::{closure#0} - 34: 0x107b9b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10828f538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x108fdfe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x1091ce510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x107831f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10787a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1078716d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x107881ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x109ca8850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_27_22-78527.txt b/rustc-ice-2026-06-16T10_27_22-78527.txt deleted file mode 100644 index 31d7a14d076..00000000000 --- a/rustc-ice-2026-06-16T10_27_22-78527.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10a8efe20 - ::create - 1: 0x1084df690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10a901e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10a8e5dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10a8da7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10a8e73d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10a9ccc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10aaa8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x109c030e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x109c7d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109e13de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10a5617e4 - ::evaluate_obligation - 12: 0x10a561ba0 - ::evaluate_obligation_no_overflow - 13: 0x10a4cc3a4 - ::process_trait_obligation - 14: 0x10a57fba4 - ::process_obligation - 15: 0x10a305c78 - >::process_obligations:: - 16: 0x108c716ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x108bad7ac - ::check_argument_types - 18: 0x108b6c234 - ::confirm_builtin_call - 19: 0x108b53854 - ::check_expr_kind - 20: 0x108b69010 - ::check_expr_with_expectation_and_args - 21: 0x108badbe0 - ::check_argument_types - 22: 0x108b510c4 - ::check_expr_kind - 23: 0x108b69010 - ::check_expr_with_expectation_and_args - 24: 0x108ba9b18 - ::check_expr_block - 25: 0x108b69010 - ::check_expr_with_expectation_and_args - 26: 0x108b66fd4 - ::check_return_or_body_tail - 27: 0x108c43fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x108b102dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x108b39b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x109c205c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x109dfb2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x1086e9154 - ::typeck:: - 33: 0x1087c490c - ::par_hir_body_owners::::{closure#0} - 34: 0x1087ff84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108ef3538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x109c43e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109e32510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x108495f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x1084de7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1084d56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x1084e5ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10a90c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_27_22-78528.txt b/rustc-ice-2026-06-16T10_27_22-78528.txt deleted file mode 100644 index e7d5dad8b40..00000000000 --- a/rustc-ice-2026-06-16T10_27_22-78528.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10dcdfe20 - ::create - 1: 0x10b8cf690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10dcf1e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10dcd5dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10dcca7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10dcd73d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10ddbcc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10de98fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10cff30e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10d06d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10d203de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10d9517e4 - ::evaluate_obligation - 12: 0x10d951ba0 - ::evaluate_obligation_no_overflow - 13: 0x10d8bc3a4 - ::process_trait_obligation - 14: 0x10d96fba4 - ::process_obligation - 15: 0x10d6f5c78 - >::process_obligations:: - 16: 0x10c0616ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10bf9d7ac - ::check_argument_types - 18: 0x10bf5c234 - ::confirm_builtin_call - 19: 0x10bf43854 - ::check_expr_kind - 20: 0x10bf59010 - ::check_expr_with_expectation_and_args - 21: 0x10bf9dbe0 - ::check_argument_types - 22: 0x10bf410c4 - ::check_expr_kind - 23: 0x10bf59010 - ::check_expr_with_expectation_and_args - 24: 0x10bf99b18 - ::check_expr_block - 25: 0x10bf59010 - ::check_expr_with_expectation_and_args - 26: 0x10bf56fd4 - ::check_return_or_body_tail - 27: 0x10c033fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10bf002dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10bf29b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10d0105c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10d1eb2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10bad9154 - ::typeck:: - 33: 0x10bbb490c - ::par_hir_body_owners::::{closure#0} - 34: 0x10bbef84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10c2e3538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10d033e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10d222510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10b885f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10b8ce7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10b8c56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10b8d5ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10dcfc850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_28_07-79736.txt b/rustc-ice-2026-06-16T10_28_07-79736.txt deleted file mode 100644 index 1464294d68b..00000000000 --- a/rustc-ice-2026-06-16T10_28_07-79736.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10bf73e20 - ::create - 1: 0x109b63690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10bf85e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10bf69dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10bf5e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10bf6b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c050c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c12cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10b2870e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10b301744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10b497de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10bbe57e4 - ::evaluate_obligation - 12: 0x10bbe5ba0 - ::evaluate_obligation_no_overflow - 13: 0x10bb503a4 - ::process_trait_obligation - 14: 0x10bc03ba4 - ::process_obligation - 15: 0x10b989c78 - >::process_obligations:: - 16: 0x10a2f56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a2317ac - ::check_argument_types - 18: 0x10a1f0234 - ::confirm_builtin_call - 19: 0x10a1d7854 - ::check_expr_kind - 20: 0x10a1ed010 - ::check_expr_with_expectation_and_args - 21: 0x10a231be0 - ::check_argument_types - 22: 0x10a1d50c4 - ::check_expr_kind - 23: 0x10a1ed010 - ::check_expr_with_expectation_and_args - 24: 0x10a22db18 - ::check_expr_block - 25: 0x10a1ed010 - ::check_expr_with_expectation_and_args - 26: 0x10a1eafd4 - ::check_return_or_body_tail - 27: 0x10a2c7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a1942dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a1bdb44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10b2a45c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10b47f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x109d6d154 - ::typeck:: - 33: 0x109e4890c - ::par_hir_body_owners::::{closure#0} - 34: 0x109e8384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10a577538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10b2c7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10b4b6510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x109b19f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x109b627d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x109b596d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x109b69ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10bf90850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_28_07-79737.txt b/rustc-ice-2026-06-16T10_28_07-79737.txt deleted file mode 100644 index 2ca44c868a4..00000000000 --- a/rustc-ice-2026-06-16T10_28_07-79737.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10a43be20 - ::create - 1: 0x10802b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10a44de30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10a431dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10a4267c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10a4333d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10a518c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10a5f4fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10974f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x1097c9744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10995fde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10a0ad7e4 - ::evaluate_obligation - 12: 0x10a0adba0 - ::evaluate_obligation_no_overflow - 13: 0x10a0183a4 - ::process_trait_obligation - 14: 0x10a0cbba4 - ::process_obligation - 15: 0x109e51c78 - >::process_obligations:: - 16: 0x1087bd6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x1086f97ac - ::check_argument_types - 18: 0x1086b8234 - ::confirm_builtin_call - 19: 0x10869f854 - ::check_expr_kind - 20: 0x1086b5010 - ::check_expr_with_expectation_and_args - 21: 0x1086f9be0 - ::check_argument_types - 22: 0x10869d0c4 - ::check_expr_kind - 23: 0x1086b5010 - ::check_expr_with_expectation_and_args - 24: 0x1086f5b18 - ::check_expr_block - 25: 0x1086b5010 - ::check_expr_with_expectation_and_args - 26: 0x1086b2fd4 - ::check_return_or_body_tail - 27: 0x10878ffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10865c2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x108685b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10976c5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x1099472ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x108235154 - ::typeck:: - 33: 0x10831090c - ::par_hir_body_owners::::{closure#0} - 34: 0x10834b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108a3f538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10978fe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10997e510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x107fe1f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10802a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1080216d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x108031ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10a458850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_28_17-80378.txt b/rustc-ice-2026-06-16T10_28_17-80378.txt deleted file mode 100644 index 1258b3ffa87..00000000000 --- a/rustc-ice-2026-06-16T10_28_17-80378.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10e843e20 - ::create - 1: 0x10c433690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10e855e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10e839dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10e82e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10e83b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10e920c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10e9fcfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10db570e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10dbd1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10dd67de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10e4b57e4 - ::evaluate_obligation - 12: 0x10e4b5ba0 - ::evaluate_obligation_no_overflow - 13: 0x10e4203a4 - ::process_trait_obligation - 14: 0x10e4d3ba4 - ::process_obligation - 15: 0x10e259c78 - >::process_obligations:: - 16: 0x10cbc56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10cb017ac - ::check_argument_types - 18: 0x10cac0234 - ::confirm_builtin_call - 19: 0x10caa7854 - ::check_expr_kind - 20: 0x10cabd010 - ::check_expr_with_expectation_and_args - 21: 0x10cb01be0 - ::check_argument_types - 22: 0x10caa50c4 - ::check_expr_kind - 23: 0x10cabd010 - ::check_expr_with_expectation_and_args - 24: 0x10cafdb18 - ::check_expr_block - 25: 0x10cabd010 - ::check_expr_with_expectation_and_args - 26: 0x10cabafd4 - ::check_return_or_body_tail - 27: 0x10cb97fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10ca642dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10ca8db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10db745c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10dd4f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10c63d154 - ::typeck:: - 33: 0x10c71890c - ::par_hir_body_owners::::{closure#0} - 34: 0x10c75384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10ce47538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10db97e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10dd86510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10c3e9f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10c4327d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10c4296d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10c439ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10e860850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_28_17-80379.txt b/rustc-ice-2026-06-16T10_28_17-80379.txt deleted file mode 100644 index ced0e2dd7a5..00000000000 --- a/rustc-ice-2026-06-16T10_28_17-80379.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10c69be20 - ::create - 1: 0x10a28b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10c6ade30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10c691dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10c6867c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10c6933d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c778c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c854fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10b9af0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10ba29744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10bbbfde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10c30d7e4 - ::evaluate_obligation - 12: 0x10c30dba0 - ::evaluate_obligation_no_overflow - 13: 0x10c2783a4 - ::process_trait_obligation - 14: 0x10c32bba4 - ::process_obligation - 15: 0x10c0b1c78 - >::process_obligations:: - 16: 0x10aa1d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a9597ac - ::check_argument_types - 18: 0x10a918234 - ::confirm_builtin_call - 19: 0x10a8ff854 - ::check_expr_kind - 20: 0x10a915010 - ::check_expr_with_expectation_and_args - 21: 0x10a959be0 - ::check_argument_types - 22: 0x10a8fd0c4 - ::check_expr_kind - 23: 0x10a915010 - ::check_expr_with_expectation_and_args - 24: 0x10a955b18 - ::check_expr_block - 25: 0x10a915010 - ::check_expr_with_expectation_and_args - 26: 0x10a912fd4 - ::check_return_or_body_tail - 27: 0x10a9effc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a8bc2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a8e5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10b9cc5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10bba72ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10a495154 - ::typeck:: - 33: 0x10a57090c - ::par_hir_body_owners::::{closure#0} - 34: 0x10a5ab84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10ac9f538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10b9efe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10bbde510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10a241f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10a28a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10a2816d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10a291ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10c6b8850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_31_12-83834.txt b/rustc-ice-2026-06-16T10_31_12-83834.txt deleted file mode 100644 index a3e660b5842..00000000000 --- a/rustc-ice-2026-06-16T10_31_12-83834.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x109dc7e20 - ::create - 1: 0x1079b7690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x109dd9e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x109dbddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x109db27c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x109dbf3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x109ea4c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x109f80fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x1090db0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x109155744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x1092ebde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x109a397e4 - ::evaluate_obligation - 12: 0x109a39ba0 - ::evaluate_obligation_no_overflow - 13: 0x1099a43a4 - ::process_trait_obligation - 14: 0x109a57ba4 - ::process_obligation - 15: 0x1097ddc78 - >::process_obligations:: - 16: 0x1081496ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x1080857ac - ::check_argument_types - 18: 0x108044234 - ::confirm_builtin_call - 19: 0x10802b854 - ::check_expr_kind - 20: 0x108041010 - ::check_expr_with_expectation_and_args - 21: 0x108085be0 - ::check_argument_types - 22: 0x1080290c4 - ::check_expr_kind - 23: 0x108041010 - ::check_expr_with_expectation_and_args - 24: 0x108081b18 - ::check_expr_block - 25: 0x108041010 - ::check_expr_with_expectation_and_args - 26: 0x10803efd4 - ::check_return_or_body_tail - 27: 0x10811bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x107fe82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x108011b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x1090f85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x1092d32ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107bc1154 - ::typeck:: - 33: 0x107c9c90c - ::par_hir_body_owners::::{closure#0} - 34: 0x107cd784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x1083cb538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10911be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10930a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10796df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x1079b67d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1079ad6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x1079bdccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x109de4850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_31_12-83835.txt b/rustc-ice-2026-06-16T10_31_12-83835.txt deleted file mode 100644 index 1cf6043e3cb..00000000000 --- a/rustc-ice-2026-06-16T10_31_12-83835.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10a10fe20 - ::create - 1: 0x107cff690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10a121e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10a105dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10a0fa7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10a1073d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10a1ecc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10a2c8fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x1094230e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10949d744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109633de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x109d817e4 - ::evaluate_obligation - 12: 0x109d81ba0 - ::evaluate_obligation_no_overflow - 13: 0x109cec3a4 - ::process_trait_obligation - 14: 0x109d9fba4 - ::process_obligation - 15: 0x109b25c78 - >::process_obligations:: - 16: 0x1084916ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x1083cd7ac - ::check_argument_types - 18: 0x10838c234 - ::confirm_builtin_call - 19: 0x108373854 - ::check_expr_kind - 20: 0x108389010 - ::check_expr_with_expectation_and_args - 21: 0x1083cdbe0 - ::check_argument_types - 22: 0x1083710c4 - ::check_expr_kind - 23: 0x108389010 - ::check_expr_with_expectation_and_args - 24: 0x1083c9b18 - ::check_expr_block - 25: 0x108389010 - ::check_expr_with_expectation_and_args - 26: 0x108386fd4 - ::check_return_or_body_tail - 27: 0x108463fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x1083302dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x108359b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x1094405c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10961b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x107f09154 - ::typeck:: - 33: 0x107fe490c - ::par_hir_body_owners::::{closure#0} - 34: 0x10801f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108713538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x109463e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109652510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x107cb5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x107cfe7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x107cf56d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x107d05ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10a12c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_31_15-84148.txt b/rustc-ice-2026-06-16T10_31_15-84148.txt deleted file mode 100644 index 883be8bafa6..00000000000 --- a/rustc-ice-2026-06-16T10_31_15-84148.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10a54fe20 - ::create - 1: 0x10813f690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10a561e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10a545dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10a53a7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10a5473d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10a62cc78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10a708fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x1098630e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x1098dd744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x109a73de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10a1c17e4 - ::evaluate_obligation - 12: 0x10a1c1ba0 - ::evaluate_obligation_no_overflow - 13: 0x10a12c3a4 - ::process_trait_obligation - 14: 0x10a1dfba4 - ::process_obligation - 15: 0x109f65c78 - >::process_obligations:: - 16: 0x1088d16ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10880d7ac - ::check_argument_types - 18: 0x1087cc234 - ::confirm_builtin_call - 19: 0x1087b3854 - ::check_expr_kind - 20: 0x1087c9010 - ::check_expr_with_expectation_and_args - 21: 0x10880dbe0 - ::check_argument_types - 22: 0x1087b10c4 - ::check_expr_kind - 23: 0x1087c9010 - ::check_expr_with_expectation_and_args - 24: 0x108809b18 - ::check_expr_block - 25: 0x1087c9010 - ::check_expr_with_expectation_and_args - 26: 0x1087c6fd4 - ::check_return_or_body_tail - 27: 0x1088a3fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x1087702dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x108799b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x1098805c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x109a5b2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x108349154 - ::typeck:: - 33: 0x10842490c - ::par_hir_body_owners::::{closure#0} - 34: 0x10845f84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x108b53538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x1098a3e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x109a92510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x1080f5f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10813e7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1081356d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x108145ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10a56c850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_31_15-84149.txt b/rustc-ice-2026-06-16T10_31_15-84149.txt deleted file mode 100644 index 5563724de21..00000000000 --- a/rustc-ice-2026-06-16T10_31_15-84149.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10be63e20 - ::create - 1: 0x109a53690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10be75e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10be59dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10be4e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10be5b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10bf40c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c01cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10b1770e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10b1f1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10b387de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10bad57e4 - ::evaluate_obligation - 12: 0x10bad5ba0 - ::evaluate_obligation_no_overflow - 13: 0x10ba403a4 - ::process_trait_obligation - 14: 0x10baf3ba4 - ::process_obligation - 15: 0x10b879c78 - >::process_obligations:: - 16: 0x10a1e56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a1217ac - ::check_argument_types - 18: 0x10a0e0234 - ::confirm_builtin_call - 19: 0x10a0c7854 - ::check_expr_kind - 20: 0x10a0dd010 - ::check_expr_with_expectation_and_args - 21: 0x10a121be0 - ::check_argument_types - 22: 0x10a0c50c4 - ::check_expr_kind - 23: 0x10a0dd010 - ::check_expr_with_expectation_and_args - 24: 0x10a11db18 - ::check_expr_block - 25: 0x10a0dd010 - ::check_expr_with_expectation_and_args - 26: 0x10a0dafd4 - ::check_return_or_body_tail - 27: 0x10a1b7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a0842dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a0adb44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10b1945c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10b36f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x109c5d154 - ::typeck:: - 33: 0x109d3890c - ::par_hir_body_owners::::{closure#0} - 34: 0x109d7384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10a467538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10b1b7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10b3a6510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x109a09f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x109a527d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x109a496d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x109a59ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10be80850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_31_35-84493.txt b/rustc-ice-2026-06-16T10_31_35-84493.txt deleted file mode 100644 index 85667fc5df9..00000000000 --- a/rustc-ice-2026-06-16T10_31_35-84493.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10b8d7e20 - ::create - 1: 0x1094c7690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10b8e9e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10b8cddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10b8c27c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10b8cf3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10b9b4c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10ba90fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10abeb0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10ac65744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10adfbde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10b5497e4 - ::evaluate_obligation - 12: 0x10b549ba0 - ::evaluate_obligation_no_overflow - 13: 0x10b4b43a4 - ::process_trait_obligation - 14: 0x10b567ba4 - ::process_obligation - 15: 0x10b2edc78 - >::process_obligations:: - 16: 0x109c596ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x109b957ac - ::check_argument_types - 18: 0x109b54234 - ::confirm_builtin_call - 19: 0x109b3b854 - ::check_expr_kind - 20: 0x109b51010 - ::check_expr_with_expectation_and_args - 21: 0x109b95be0 - ::check_argument_types - 22: 0x109b390c4 - ::check_expr_kind - 23: 0x109b51010 - ::check_expr_with_expectation_and_args - 24: 0x109b91b18 - ::check_expr_block - 25: 0x109b51010 - ::check_expr_with_expectation_and_args - 26: 0x109b4efd4 - ::check_return_or_body_tail - 27: 0x109c2bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x109af82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x109b21b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10ac085c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10ade32ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x1096d1154 - ::typeck:: - 33: 0x1097ac90c - ::par_hir_body_owners::::{closure#0} - 34: 0x1097e784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x109edb538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10ac2be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10ae1a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10947df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x1094c67d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x1094bd6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x1094cdccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10b8f4850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_31_35-84494.txt b/rustc-ice-2026-06-16T10_31_35-84494.txt deleted file mode 100644 index d97645d104d..00000000000 --- a/rustc-ice-2026-06-16T10_31_35-84494.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10c05be20 - ::create - 1: 0x109c4b690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10c06de30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10c051dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10c0467c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10c0533d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c138c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c214fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10b36f0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10b3e9744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10b57fde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10bccd7e4 - ::evaluate_obligation - 12: 0x10bccdba0 - ::evaluate_obligation_no_overflow - 13: 0x10bc383a4 - ::process_trait_obligation - 14: 0x10bcebba4 - ::process_obligation - 15: 0x10ba71c78 - >::process_obligations:: - 16: 0x10a3dd6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a3197ac - ::check_argument_types - 18: 0x10a2d8234 - ::confirm_builtin_call - 19: 0x10a2bf854 - ::check_expr_kind - 20: 0x10a2d5010 - ::check_expr_with_expectation_and_args - 21: 0x10a319be0 - ::check_argument_types - 22: 0x10a2bd0c4 - ::check_expr_kind - 23: 0x10a2d5010 - ::check_expr_with_expectation_and_args - 24: 0x10a315b18 - ::check_expr_block - 25: 0x10a2d5010 - ::check_expr_with_expectation_and_args - 26: 0x10a2d2fd4 - ::check_return_or_body_tail - 27: 0x10a3affc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a27c2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a2a5b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10b38c5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10b5672ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x109e55154 - ::typeck:: - 33: 0x109f3090c - ::par_hir_body_owners::::{closure#0} - 34: 0x109f6b84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10a65f538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10b3afe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10b59e510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x109c01f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x109c4a7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x109c416d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x109c51ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10c078850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_32_02-84897.txt b/rustc-ice-2026-06-16T10_32_02-84897.txt deleted file mode 100644 index df5ac3614d5..00000000000 --- a/rustc-ice-2026-06-16T10_32_02-84897.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10e553e20 - ::create - 1: 0x10c143690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10e565e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10e549dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10e53e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10e54b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10e630c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10e70cfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10d8670e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10d8e1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10da77de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10e1c57e4 - ::evaluate_obligation - 12: 0x10e1c5ba0 - ::evaluate_obligation_no_overflow - 13: 0x10e1303a4 - ::process_trait_obligation - 14: 0x10e1e3ba4 - ::process_obligation - 15: 0x10df69c78 - >::process_obligations:: - 16: 0x10c8d56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10c8117ac - ::check_argument_types - 18: 0x10c7d0234 - ::confirm_builtin_call - 19: 0x10c7b7854 - ::check_expr_kind - 20: 0x10c7cd010 - ::check_expr_with_expectation_and_args - 21: 0x10c811be0 - ::check_argument_types - 22: 0x10c7b50c4 - ::check_expr_kind - 23: 0x10c7cd010 - ::check_expr_with_expectation_and_args - 24: 0x10c80db18 - ::check_expr_block - 25: 0x10c7cd010 - ::check_expr_with_expectation_and_args - 26: 0x10c7cafd4 - ::check_return_or_body_tail - 27: 0x10c8a7fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10c7742dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10c79db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10d8845c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10da5f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10c34d154 - ::typeck:: - 33: 0x10c42890c - ::par_hir_body_owners::::{closure#0} - 34: 0x10c46384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10cb57538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10d8a7e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10da96510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10c0f9f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10c1427d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10c1396d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10c149ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10e570850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_32_02-84898.txt b/rustc-ice-2026-06-16T10_32_02-84898.txt deleted file mode 100644 index 2ed4e73be16..00000000000 --- a/rustc-ice-2026-06-16T10_32_02-84898.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10ddebe20 - ::create - 1: 0x10b9db690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10ddfde30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10dde1dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10ddd67c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10dde33d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10dec8c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10dfa4fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10d0ff0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10d179744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10d30fde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10da5d7e4 - ::evaluate_obligation - 12: 0x10da5dba0 - ::evaluate_obligation_no_overflow - 13: 0x10d9c83a4 - ::process_trait_obligation - 14: 0x10da7bba4 - ::process_obligation - 15: 0x10d801c78 - >::process_obligations:: - 16: 0x10c16d6ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10c0a97ac - ::check_argument_types - 18: 0x10c068234 - ::confirm_builtin_call - 19: 0x10c04f854 - ::check_expr_kind - 20: 0x10c065010 - ::check_expr_with_expectation_and_args - 21: 0x10c0a9be0 - ::check_argument_types - 22: 0x10c04d0c4 - ::check_expr_kind - 23: 0x10c065010 - ::check_expr_with_expectation_and_args - 24: 0x10c0a5b18 - ::check_expr_block - 25: 0x10c065010 - ::check_expr_with_expectation_and_args - 26: 0x10c062fd4 - ::check_return_or_body_tail - 27: 0x10c13ffc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10c00c2dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10c035b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10d11c5c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10d2f72ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10bbe5154 - ::typeck:: - 33: 0x10bcc090c - ::par_hir_body_owners::::{closure#0} - 34: 0x10bcfb84c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10c3ef538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10d13fe4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10d32e510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10b991f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10b9da7d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10b9d16d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10b9e1ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10de08850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_32_06-85213.txt b/rustc-ice-2026-06-16T10_32_06-85213.txt deleted file mode 100644 index 374510d8c28..00000000000 --- a/rustc-ice-2026-06-16T10_32_06-85213.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10dc33e20 - ::create - 1: 0x10b823690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10dc45e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10dc29dfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10dc1e7c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10dc2b3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10dd10c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10ddecfdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10cf470e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10cfc1744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10d157de8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10d8a57e4 - ::evaluate_obligation - 12: 0x10d8a5ba0 - ::evaluate_obligation_no_overflow - 13: 0x10d8103a4 - ::process_trait_obligation - 14: 0x10d8c3ba4 - ::process_obligation - 15: 0x10d649c78 - >::process_obligations:: - 16: 0x10bfb56ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10bef17ac - ::check_argument_types - 18: 0x10beb0234 - ::confirm_builtin_call - 19: 0x10be97854 - ::check_expr_kind - 20: 0x10bead010 - ::check_expr_with_expectation_and_args - 21: 0x10bef1be0 - ::check_argument_types - 22: 0x10be950c4 - ::check_expr_kind - 23: 0x10bead010 - ::check_expr_with_expectation_and_args - 24: 0x10beedb18 - ::check_expr_block - 25: 0x10bead010 - ::check_expr_with_expectation_and_args - 26: 0x10beaafd4 - ::check_return_or_body_tail - 27: 0x10bf87fc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10be542dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10be7db44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10cf645c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10d13f2ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10ba2d154 - ::typeck:: - 33: 0x10bb0890c - ::par_hir_body_owners::::{closure#0} - 34: 0x10bb4384c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10c237538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10cf87e4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10d176510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10b7d9f8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10b8227d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10b8196d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10b829ccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10dc50850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack diff --git a/rustc-ice-2026-06-16T10_32_06-85214.txt b/rustc-ice-2026-06-16T10_32_06-85214.txt deleted file mode 100644 index aa57c4141c8..00000000000 --- a/rustc-ice-2026-06-16T10_32_06-85214.txt +++ /dev/null @@ -1,57 +0,0 @@ -thread 'rustc' panicked at /rustc-dev/f20a92ec01483dc5c58e90e246f266bdad822d86/compiler/rustc_middle/src/verify_ich.rs:82:9: -Found unstable fingerprints for evaluate_obligation(7b9135564fd9a7b8-1d585ead042e024d): Ok(EvaluatedToOk) -stack backtrace: - 0: 0x10c4c7e20 - ::create - 1: 0x10a0b7690 - std[4eea21f33c263573]::panicking::update_hook::>::{closure#0} - 2: 0x10c4d9e30 - std[4eea21f33c263573]::panicking::panic_with_hook - 3: 0x10c4bddfc - std[4eea21f33c263573]::panicking::panic_handler::{closure#0} - 4: 0x10c4b27c8 - std[4eea21f33c263573]::sys::backtrace::__rust_end_short_backtrace:: - 5: 0x10c4bf3d0 - __rustc[4d02e5393822de8d]::rust_begin_unwind - 6: 0x10c5a4c78 - core[fc7f7519bc2bea21]::panicking::panic_fmt - 7: 0x10c680fdc - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich_failed - 8: 0x10b7db0e4 - rustc_middle[559fd0285f0abb56]::verify_ich::incremental_verify_ich::> - 9: 0x10b855744 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, rustc_middle[559fd0285f0abb56]::query::erase::ErasedData<[u8; 2usize]>>, true> - 10: 0x10b9ebde8 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::evaluate_obligation::execute_query_incr::__rust_end_short_backtrace - 11: 0x10c1397e4 - ::evaluate_obligation - 12: 0x10c139ba0 - ::evaluate_obligation_no_overflow - 13: 0x10c0a43a4 - ::process_trait_obligation - 14: 0x10c157ba4 - ::process_obligation - 15: 0x10beddc78 - >::process_obligations:: - 16: 0x10a8496ec - as rustc_infer[2361312dd77de7c4]::traits::engine::TraitEngine>::try_evaluate_obligations - 17: 0x10a7857ac - ::check_argument_types - 18: 0x10a744234 - ::confirm_builtin_call - 19: 0x10a72b854 - ::check_expr_kind - 20: 0x10a741010 - ::check_expr_with_expectation_and_args - 21: 0x10a785be0 - ::check_argument_types - 22: 0x10a7290c4 - ::check_expr_kind - 23: 0x10a741010 - ::check_expr_with_expectation_and_args - 24: 0x10a781b18 - ::check_expr_block - 25: 0x10a741010 - ::check_expr_with_expectation_and_args - 26: 0x10a73efd4 - ::check_return_or_body_tail - 27: 0x10a81bfc0 - rustc_hir_typeck[281f4551ef040a61]::check::check_fn - 28: 0x10a6e82dc - rustc_hir_typeck[281f4551ef040a61]::typeck_with_inspect::{closure#0} - 29: 0x10a711b44 - rustc_hir_typeck[281f4551ef040a61]::typeck_root - 30: 0x10b7f85c8 - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::, rustc_middle[559fd0285f0abb56]::dep_graph::graph::DepNodeIndex>, true> - 31: 0x10b9d32ac - rustc_query_impl[9dc2780f07d6dedd]::query_impl::typeck_root::execute_query_incr::__rust_end_short_backtrace - 32: 0x10a2c1154 - ::typeck:: - 33: 0x10a39c90c - ::par_hir_body_owners::::{closure#0} - 34: 0x10a3d784c - rustc_hir_analysis[67af799a5e1a3d9d]::check_crate - 35: 0x10aacb538 - rustc_interface[12658516f1d6a68f]::passes::analysis - 36: 0x10b81be4c - rustc_query_impl[9dc2780f07d6dedd]::execution::try_execute_query::>, true> - 37: 0x10ba0a510 - rustc_query_impl[9dc2780f07d6dedd]::query_impl::analysis::execute_query_incr::__rust_end_short_backtrace - 38: 0x10a06df8c - rustc_interface[12658516f1d6a68f]::passes::create_and_enter_global_ctxt::, rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}::{closure#2}> - 39: 0x10a0b67d8 - rustc_interface[12658516f1d6a68f]::interface::run_compiler::<(), rustc_driver_impl[54652e742a19b51e]::run_compiler::{closure#0}>::{closure#1} - 40: 0x10a0ad6d8 - std[4eea21f33c263573]::sys::backtrace::__rust_begin_short_backtrace::::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()> - 41: 0x10a0bdccc - ::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}, ()>::{closure#1} as core[fc7f7519bc2bea21]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0} - 42: 0x10c4e4850 - ::new::thread_start - 43: 0x18995fc58 - __pthread_cond_wait - - -rustc version: 1.98.0-nightly (f20a92ec0 2026-06-07) -platform: aarch64-apple-darwin - -query stack during panic: -#0 [evaluate_obligation] evaluating trait selection obligation `lock_api::mutex::Mutex: core::marker::Sync` -#1 [typeck_root] type-checking `rest::hashql::::routes` -#2 [analysis] running analysis passes on crate `hash_graph_api` -end of query stack From 2902f802cbbfd7ded4fc3bbb22bf98c931a82ea4 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:08:07 +0200 Subject: [PATCH 05/34] feat: checkpoint --- libs/@local/hashql/eval/AUTHORIZATION.md | 434 ++++++++++++++++++ libs/@local/hashql/eval/Cargo.toml | 52 +-- libs/@local/hashql/eval/src/lib.rs | 3 +- .../hashql/eval/src/postgres/authorization.rs | 189 ++++++++ libs/@local/hashql/eval/src/postgres/mod.rs | 3 + .../hashql/eval/src/postgres/projections.rs | 15 +- 6 files changed, 662 insertions(+), 34 deletions(-) create mode 100644 libs/@local/hashql/eval/AUTHORIZATION.md create mode 100644 libs/@local/hashql/eval/src/postgres/authorization.rs diff --git a/libs/@local/hashql/eval/AUTHORIZATION.md b/libs/@local/hashql/eval/AUTHORIZATION.md new file mode 100644 index 00000000000..a096d58a11b --- /dev/null +++ b/libs/@local/hashql/eval/AUTHORIZATION.md @@ -0,0 +1,434 @@ +# Authorization patching for compiled queries + +Design spec for applying actor-specific authorization to actor-agnostic compiled +queries. Patching happens before the orchestrator receives the artifacts; the +execution engine stays oblivious to authorization decisions. + +## Invariant + +Compilation is actor-agnostic. The compiler produces the same `PreparedQueries` +regardless of who asks. Authorization is a runtime concern: the policy decision +matrix arrives per request, and the compiled artifacts are cloned and patched +before entering the orchestrator. + +The orchestrator does not change. It receives already-patched artifacts and +executes them as it always has. + +## Flow + +``` +Compile + | + v +PreparedQueries (shared, immutable, actor-agnostic) + | + | clone (per request) + v +PreparedQueries (owned) + | + | authorization::graft(&mut self, &PolicyComponents, &AuthorizationSettings) + v +PreparedQueries (patched: statement changes + auxiliary params) + | + v +Orchestrator::new(client, &patched_queries, context) + | + | fulfill_in: encode parameters + auxiliary_parameters, execute + v +Postgres +``` + +## Scope: parity with the current API + +The first implementation mirrors what `PolicyComponents` + +`Filter::for_policies` + `SelectCompiler::add_filter` do today in the old +`query_entities` path. Same policy decisions, clean compile/runtime split. + +## Two concerns, one graft + +The graft performs two independent modifications to each `PreparedQuery`: + +1. **Entity admission** (WHERE conditions): which rows survive. +2. **Property masking** (expression replacement): which property keys survive + within a row. + +Both are actor-specific, both modify the `SelectStatement`, and both belong +in the graft phase. + +--- + +## Entity admission + +### Condition shapes + +All admission conditions are top-level WHERE predicates or correlated EXISTS +subqueries. No FROM tree mutation for admission, no alias allocation. + +| Policy condition | SQL shape | +| ------------------------- | -------------------------------------------------------- | +| Entity UUID permit/forbid | `.entity_uuid = $N` or `= ANY($N::uuid[])` | +| Web ID permit/forbid | `.web_id = $N` or `= ANY($N::uuid[])` | +| CreatedByPrincipal | Correlated EXISTS on `entity_editions` | +| IsOfType | Correlated EXISTS on `entity_is_of_type_ids` with UNNEST | +| IsOfBaseType | Correlated EXISTS on `entity_is_of_type_ids` | + +### Base table conditions + +Entity UUID and web ID reference columns on the base table +(`entity_temporal_metadata`). These are plain WHERE predicates: + +```sql +-- permits (OR'd) +.entity_uuid = ANY($N::uuid[]) +.web_id = ANY($N::uuid[]) + +-- forbids (negated) +NOT (.entity_uuid = ANY($N::uuid[])) +``` + +### CreatedByPrincipal + +Provenance lives on `entity_editions`. Always expressed as a correlated EXISTS, +regardless of whether entity_editions is joined in the compiled query. +PostgreSQL may optimize the correlated EXISTS into a semijoin that shares +work with an existing join, though this is not guaranteed. + +```sql +EXISTS ( + SELECT 1 + FROM entity_editions ee_auth + WHERE ee_auth.entity_edition_id = .entity_edition_id + AND ee_auth.provenance->>'createdById' = $N::text +) +``` + +The actor ID uses `ActorEntityUuid::public_actor()` for anonymous actors, +matching the existing `for_resource_filter` behavior. + +### IsOfType (paired arrays) + +`entity_is_of_type_ids` stores `base_urls` (`text[]`) and `versions` +(`bigint[]`) as parallel arrays. Independent overlap on the two arrays loses +the pairing invariant (stored pairs `(A,1),(B,2)` would falsely match query +`(A,2)`). Correct form uses UNNEST: + +```sql +EXISTS ( + SELECT 1 + FROM entity_is_of_type_ids eit + CROSS JOIN LATERAL UNNEST(eit.base_urls, eit.versions) AS u(b, v) + WHERE eit.entity_edition_id = .entity_edition_id + AND u.b = $N_base::text + AND u.v = $N_version::int8 +) +``` + +### IsOfBaseType + +Base URL alone is sufficient; no UNNEST needed: + +```sql +EXISTS ( + SELECT 1 + FROM entity_is_of_type_ids eit + WHERE eit.entity_edition_id = .entity_edition_id + AND $N_base::text = ANY(eit.base_urls) +) +``` + +### Combination algebra + +Same as `Filter::for_policies`: + +``` +blank forbid -> WHERE FALSE (deny all, short circuit) +blank permit, no forbids -> no conditions (allow all) +blank permit + forbids -> WHERE NOT (f1 OR f2 OR ...) +permits only -> WHERE (p1 OR p2 OR ... OR uuid_batch OR web_batch) +permits + forbids -> WHERE (permits) AND NOT (forbids) +no permits -> WHERE FALSE (deny all) +``` + +### Instance admin bypass + +When `policy_components.is_instance_admin()` is true, no admission conditions +are added and no property masking is applied. The graft is a no-op. + +--- + +## Property masking + +### Compilation: entity_editions as a subquery + +`entity_editions` is always joined as a subquery with explicit projections, +regardless of whether masking will be applied. This is a general mechanism, not +authorization-specific: + +```sql +INNER JOIN ( + SELECT entity_edition_id, archived, confidence, provenance, + properties AS properties, + property_metadata AS property_metadata + FROM entity_editions +) AS ee ON ee.entity_edition_id = base.entity_edition_id +``` + +The `properties` and `property_metadata` projections are identity by default. +Each is a `SelectExpression::Expression` with a known alias. This gives any +runtime pass a stable injection point: scan the subquery's SELECT list, find +the entry by alias, replace the inner expression. + +If entity_editions is not joined (the query does not touch properties, +provenance, confidence, or other edition fields), the subquery does not exist +and there is nothing to mask. + +### Graft: expression replacement + +The graft locates the entity_editions subquery in the FROM tree using +`JoinMetadata::entity_editions`. It scans the subquery's SELECT list for +entries with alias `properties` and `property_metadata`, and replaces their +inner expressions: + +``` +Before: properties AS properties +After: properties - $N::text[] AS properties + +Before: property_metadata AS property_metadata +After: property_metadata - $N::text[] AS property_metadata +``` + +The replacement expression can be anything: a simple +`properties - $N::text[]` for unconditional masking, or a more complex +CASE WHEN expression for conditional per-type masking (matching the existing +`PropertyProtectionFilter` behavior that builds type-dependent mask arrays). +The injection mechanism is general; the graft builds whatever expression the +policy requires. + +The alias is unchanged, so all downstream references (`ee.properties`, +filter expressions using `ee.properties->>'path'`) transparently get the +masked version. + +### Composability + +This approach is general. The subquery form is always present when +entity_editions is joined. Any pass (authorization, future masking, etc.) can +scan the SELECT list and replace expressions. The mechanism is not coupled to +authorization. + +--- + +## Auxiliary parameters + +Authorization values (UUIDs, web IDs, mask keys, base URLs, version strings) +are runtime data with no relation to the compiler's parameter system +(`ParameterValue<'heap>`). They do not belong in `Parameters`. + +`PreparedQuery` carries a sidepiece: + +```rust +pub struct PreparedQuery<'heap, A: Allocator> { + pub vertex_type: VertexType, + pub parameters: Parameters<'heap, A>, + pub statement: SelectStatement, + pub columns: Vec, + pub join_metadata: JoinMetadata, + + /// Opaque parameter values appended after compiled parameters during + /// encoding. Added by the authorization graft. The orchestrator does + /// not interpret them. + /// + /// These are `'static` owned values (Uuid, String, Vec, etc.). + /// At encoding time the orchestrator chains borrowed references to + /// these after the compiled parameters. They are not moved into the + /// compiled parameter vector (which uses a different allocator and + /// lifetime). + pub auxiliary_parameters: Vec>, +} +``` + +### Index discipline + +Compiled parameters occupy `$1..$K` (where `K = parameters.len()`). +Auxiliary parameters occupy `$(K+1)..$(K+M)`. Expressions generated by the +graft use absolute indices starting from `K+1`. + +The graft is responsible for index correctness: each `Expression::Parameter(n)` +it emits must have a corresponding entry in `auxiliary_parameters` at position +`n - K - 1`. A mismatch is a bug in the graft, not a user error. + +### Encoding + +The orchestrator chains borrowed references: compiled params first, then +auxiliary. The auxiliary values stay in their `Vec>` +and are not moved into the compiled parameter vector (different allocator, +different lifetime): + +```rust +// Compiled parameters (existing) +let mut encoded = Vec::with_capacity_in(query.parameters.len(), alloc.clone()); +for param in query.parameters.iter() { + encoded.push(encode_parameter_in(param, ...)?); +} + +// Chain borrowed references: compiled, then auxiliary +let compiled = encoded.iter().map(|p| &**p as &(dyn ToSql + Sync)); +let auxiliary = query.auxiliary_parameters.iter().map(|p| &**p as &(dyn ToSql + Sync)); + +let response = client + .query_raw(&statement, compiled.chain(auxiliary)) + .await?; +``` + +The orchestrator does not know what auxiliary parameters mean. It just +chains them. + +--- + +## Construction: PolicyComponents to SQL + +The `authorization` module provides: + +```rust +/// Grafts authorization conditions and property masking onto compiled +/// queries. +/// +/// The caller is responsible for cloning `PreparedQueries` before calling +/// this. The function mutates the clone in place. +pub fn graft( + queries: &mut PreparedQueries<'_, impl Allocator>, + policy: &PolicyComponents, + settings: &AuthorizationSettings, +) +``` + +`AuthorizationSettings` carries the filter protection config (which property +keys to mask, per entity type or globally). + +### Internal steps + +1. **Pre-analyze** the policy (once per graft, reused across queries): + - Iterate `policy.extract_filter_policies(ActionName::ViewEntity)` + - Separate permits and forbids + - Collect optimizable UUID/web batches (mirrors `OptimizationData`) + - Normalize actor ID for provenance + +2. **Per query**, build admission conditions: + - Read `param_offset = query.parameters.len()` + - Lower each policy decision to `Expression` nodes referencing `$N` + from the offset + - Collect corresponding `Box` values + - Combine with the permit/forbid algebra + - Push into `statement.where_expression` + - Store values in `auxiliary_parameters` + +3. **Per query**, apply property mask (if entity_editions is joined): + - Locate the entity_editions subquery via `join_metadata` + - Scan its SELECT list for `properties` / `property_metadata` aliases + - Build the mask expression (simple `column - $N::text[]` for + unconditional, or CASE WHEN for conditional per-type masking) + - Replace the inner expressions + - Add auxiliary parameters for any runtime values in the mask + +--- + +## JoinMetadata + +Captured from `Projections` after compilation: + +```rust +pub struct JoinMetadata { + /// Base table alias (entity_temporal_metadata). Always present. + pub base_alias: Alias, + /// entity_editions alias, if the query joins it. The graft uses this + /// to locate the entity_editions subquery for property masking. + pub entity_editions: Option, +} +``` + +`Projections` exposes a `snapshot()` method: + +```rust +impl Projections { + pub fn snapshot(&self) -> JoinMetadata { + JoinMetadata { + base_alias: self.base_alias, + entity_editions: self.entity_editions, + } + } +} +``` + +--- + +## Changes to existing code + +### `PostgresCompiler` + +Remove `property_mask: Option` and the `with_property_mask` builder +method. The mask moves entirely to the graft phase. + +Remove the compile-time masking logic in `compile_graph_read_entity` that wraps +property expressions with `Expression::subtract`. + +### `Projections::build_entity_editions` + +Change from joining the raw `entity_editions` table to joining a subquery with +explicit column projections. The `properties` and `property_metadata` columns +are projected as identity expressions with named aliases. + +### `PreparedQuery` + +Add `join_metadata: JoinMetadata` and `auxiliary_parameters: Vec>`fields. Initialize`auxiliary_parameters` to empty at compile time. + +### Orchestrator encoding + +Append `query.auxiliary_parameters` after compiled parameters in the encoding +loop. + +--- + +## File layout + +``` +libs/@local/hashql/eval/src/postgres/ + authorization.rs -- graft(), AuthorizationSettings, + policy analysis, condition builders + mod.rs -- PreparedQuery gains join_metadata and + auxiliary_parameters; remove + property_mask from PostgresCompiler + projections.rs -- Projections::snapshot() -> JoinMetadata; + build_entity_editions produces subquery + parameters.rs -- unchanged + +libs/@local/hashql/eval/src/orchestrator/ + request/graph_read.rs -- encoding loop appends auxiliary_parameters + mod.rs -- unchanged +``` + +--- + +## Cloning + +`Box` is not cloneable, so `PreparedQuery` cannot derive +`Clone`. Provide a `clone_for_graft()` method that clones the actor-agnostic +fields (`statement`, `parameters`, `columns`, `join_metadata`, `vertex_type`) +and initializes `auxiliary_parameters` to empty. This is the intended clone +path: callers clone the shared artifact, then graft onto the clone. + +Do not silently clone a grafted query in a way that drops auxiliary +parameters. + +--- + +## Not in scope + +- Direct-type admission semantics from auth.md. This spec provides the + grafting mechanism; the admission semantics produce the conditions. +- Property-level masking via MIR labels (the information-flow pass from + auth.md). Future work on top of this mechanism. +- Filter protection / enumeration attack mitigation beyond property masking. +- Multi-backend authorization. +- Reusing the compiled LATERAL aggregate for type filtering (structurally + different purpose). diff --git a/libs/@local/hashql/eval/Cargo.toml b/libs/@local/hashql/eval/Cargo.toml index 4e7354e993e..ae14bfb51e4 100644 --- a/libs/@local/hashql/eval/Cargo.toml +++ b/libs/@local/hashql/eval/Cargo.toml @@ -19,34 +19,34 @@ hashql-mir = { workspace = true, public = true } hashql-core = { workspace = true } # Private third-party dependencies -bytes = { workspace = true } -futures-lite = { workspace = true } -postgres-protocol = { workspace = true } -postgres-types = { workspace = true, features = ["uuid-1"] } -serde = { workspace = true } -serde_json = { workspace = true, features = ["raw_value"] } -simple-mermaid = { workspace = true } -tokio-postgres = { workspace = true } -url = { workspace = true } -uuid = { workspace = true } - -[dev-dependencies] -error-stack = { workspace = true } +bytes = { workspace = true } +futures-lite = { workspace = true } hash-graph-authorization = { workspace = true } -hash-graph-store = { workspace = true } -hash-graph-test-data = { workspace = true } -hashql-compiletest = { workspace = true } -hashql-diagnostics = { workspace = true, features = ["render"] } -insta = { workspace = true } -libtest-mimic = { workspace = true } -regex = { workspace = true } -similar-asserts = { workspace = true } -sqruff-lib = { workspace = true } -sqruff-lib-core = { workspace = true } -testcontainers = { workspace = true, features = ["reusable-containers"] } -testcontainers-modules = { workspace = true, features = ["postgres"] } -tokio = { workspace = true } +postgres-protocol = { workspace = true } +postgres-types = { workspace = true, features = ["uuid-1"] } +serde = { workspace = true } +serde_json = { workspace = true, features = ["raw_value"] } +simple-mermaid = { workspace = true } +tokio-postgres = { workspace = true } type-system = { workspace = true } +url = { workspace = true } +uuid = { workspace = true } + +[dev-dependencies] +error-stack = { workspace = true } +hash-graph-store = { workspace = true } +hash-graph-test-data = { workspace = true } +hashql-compiletest = { workspace = true } +hashql-diagnostics = { workspace = true, features = ["render"] } +insta = { workspace = true } +libtest-mimic = { workspace = true } +regex = { workspace = true } +similar-asserts = { workspace = true } +sqruff-lib = { workspace = true } +sqruff-lib-core = { workspace = true } +testcontainers = { workspace = true, features = ["reusable-containers"] } +testcontainers-modules = { workspace = true, features = ["postgres"] } +tokio = { workspace = true } [lints] workspace = true diff --git a/libs/@local/hashql/eval/src/lib.rs b/libs/@local/hashql/eval/src/lib.rs index ec19faed689..ef0fa2c5390 100644 --- a/libs/@local/hashql/eval/src/lib.rs +++ b/libs/@local/hashql/eval/src/lib.rs @@ -14,7 +14,8 @@ iter_array_chunks, maybe_uninit_fill, impl_trait_in_assoc_type, - try_blocks + try_blocks, + variant_count )] #![cfg_attr(test, feature( // Library Features diff --git a/libs/@local/hashql/eval/src/postgres/authorization.rs b/libs/@local/hashql/eval/src/postgres/authorization.rs new file mode 100644 index 00000000000..307eefa8e56 --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/authorization.rs @@ -0,0 +1,189 @@ +use core::alloc::Allocator; + +use hash_graph_authorization::policies::{ + Effect, PolicyComponents, + action::ActionName, + resource::{EntityResourceConstraint, EntityResourceFilter, ResourceConstraint}, +}; +use hash_graph_postgres_store::store::postgres::query::{ + Column, ColumnReference, Constant, Expression, TableReference, VariadicExpression, + VariadicOperator, table, +}; +use hashql_core::id::{Id, bit_vec::FiniteBitSet}; +use postgres_types::ToSql; + +use super::{PreparedQuery, projections::Projections}; + +struct AuthorizationProjections<'base> { + index: usize, + base: &'base Projections, +} + +impl<'base> AuthorizationProjections<'base> { + fn temporal_metadata(&self) -> TableReference<'static> { + self.base.temporal_metadata() + } +} + +struct AuxiliaryParameters { + initial_offset: usize, + parameters: Vec, A>, +} + +impl AuxiliaryParameters { + fn push(&mut self, value: impl ToSql + Sync + 'static) -> usize + where + A: Clone, + { + let alloc = self.parameters.allocator().clone(); + self.parameters.push(Box::new_in(value, alloc)); + + self.parameters.len() + self.initial_offset + } +} + +struct PreparedAnalysis<'query, A: Allocator> { + projections: AuthorizationProjections<'query>, + parameters: AuxiliaryParameters, +} + +impl<'query, A: Allocator> PreparedAnalysis<'query, A> { + fn new_in(query: &'query PreparedQuery<'_, impl Allocator>, alloc: A) -> Self { + Self { + projections: AuthorizationProjections { + index: query.projections.index, + base: &query.projections, + }, + parameters: AuxiliaryParameters { + initial_offset: query.parameters.len(), + parameters: Vec::new_in(alloc), + }, + } + } +} + +struct AnalysisResidual { + condition: Expression, +} + +fn convert_entity_resource_filter( + output: &mut PreparedAnalysis<'_, A>, + filter: &EntityResourceFilter, +) -> Expression { + match filter { + EntityResourceFilter::All { filters } => { + let expressions = filters + .iter() + .map(|filter| convert_entity_resource_filter(output, filter)) + .collect(); + + Expression::Variadic(VariadicExpression { + op: VariadicOperator::And, + exprs: expressions, + }) + } + EntityResourceFilter::Any { filters } => { + let expressions = filters + .iter() + .map(|filter| convert_entity_resource_filter(output, filter)) + .collect(); + + Expression::Variadic(VariadicExpression { + op: VariadicOperator::Or, + exprs: expressions, + }) + } + EntityResourceFilter::Not { filter } => { + convert_entity_resource_filter(output, filter).not() + } + EntityResourceFilter::IsOfType { entity_type } => { + todo!() + } + EntityResourceFilter::IsOfBaseType { entity_type } => todo!(), + EntityResourceFilter::CreatedByPrincipal => { + todo!() + } + } +} + +fn convert_resource_constraint( + output: &mut PreparedAnalysis, + constraint: &ResourceConstraint, +) -> Expression { + match constraint { + &ResourceConstraint::Web { web_id } => { + let index = output.parameters.push(web_id); + + let reference = Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId).into(), + }); + + let param = Expression::Parameter(index); + + Expression::equal(reference, param) + } + &ResourceConstraint::Entity(EntityResourceConstraint::Exact { id }) => { + let index = output.parameters.push(id); + + let reference = Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) + .into(), + }); + + let param = Expression::Parameter(index); + + Expression::equal(reference, param) + } + ResourceConstraint::Entity(EntityResourceConstraint::Web { web_id, filter }) => { + let lhs = + convert_resource_constraint(output, &ResourceConstraint::Web { web_id: *web_id }); + let rhs = convert_entity_resource_filter(output, filter); + + Expression::all(vec![lhs, rhs]) + } + ResourceConstraint::Entity(EntityResourceConstraint::Any { filter }) => { + convert_entity_resource_filter(output, filter) + } + ResourceConstraint::EntityType(_) + | ResourceConstraint::PropertyType(_) + | ResourceConstraint::DataType(_) + | ResourceConstraint::Meta(_) => Expression::Constant(Constant::Boolean(false)), + } +} + +fn analysis_in( + query: &PreparedQuery<'_, impl Allocator>, + policy: &PolicyComponents, + alloc: A, +) -> AnalysisResidual { + let policies = policy.extract_filter_policies(match query.vertex_type { + hashql_mir::pass::execution::VertexType::Entity => ActionName::ViewEntity, + }); + + let mut output = PreparedAnalysis::new_in(query, alloc); + let mut permits = Vec::new(); + let mut forbids = Vec::new(); + let mut blank_permit = false; + + for (effect, constraint) in policies { + match (effect, constraint) { + (Effect::Permit, _) if blank_permit => {} + (Effect::Permit, None) => blank_permit = true, + (Effect::Forbid, None) => { + return AnalysisResidual { + condition: Expression::Constant(Constant::Boolean(false)), + }; + } + (Effect::Permit, Some(constraint)) => { + permits.push(convert_resource_constraint(&mut output, constraint)); + } + (Effect::Forbid, Some(constraint)) => { + forbids.push(convert_resource_constraint(&mut output, constraint)); + } + } + } + + todo!() +} diff --git a/libs/@local/hashql/eval/src/postgres/mod.rs b/libs/@local/hashql/eval/src/postgres/mod.rs index 0f18a5f1f00..3257df634b3 100644 --- a/libs/@local/hashql/eval/src/postgres/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/mod.rs @@ -66,6 +66,7 @@ pub use self::{ }; use crate::context::CodeGenerationContext; +mod authorization; mod continuation; pub(crate) mod error; mod filter; @@ -196,6 +197,7 @@ impl Display for ColumnDescriptor { pub struct PreparedQuery<'heap, A: Allocator> { pub vertex_type: VertexType, pub parameters: Parameters<'heap, A>, + pub projections: Projections, pub statement: SelectStatement, pub columns: Vec, } @@ -502,6 +504,7 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> vertex_type: VertexType::Entity, parameters: db.parameters, statement: query, + projections: db.projections, columns, } } diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index 66a55aa58f6..80b99c5ae97 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -32,17 +32,18 @@ impl From for ColumnName<'_> { /// /// Accessors like [`Self::entity_editions`] register that a table is needed and return a /// reference to it. The actual `FROM` tree is built once at the end via [`Self::build_from`]. +#[derive(Debug, Clone)] pub(crate) struct Projections { - index: usize, + pub(crate) index: usize, /// Always present as the base table; everything joins through it. - base_alias: Alias, + pub base_alias: Alias, - entity_editions: Option, - entity_ids: Option, - entity_type_ids: Option, - left: Option, - right: Option, + pub entity_editions: Option, + pub entity_ids: Option, + pub entity_type_ids: Option, + pub left: Option, + pub right: Option, } impl Projections { From aaff5557862d83142141076422806ed963450cae Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:28:54 +0200 Subject: [PATCH 06/34] feat: checkpoint --- .../postgres/query/expression/conditional.rs | 14 + libs/@local/hashql/eval/Cargo.toml | 1 + .../hashql/eval/src/postgres/authorization.rs | 469 +++++++++++++++--- 3 files changed, 423 insertions(+), 61 deletions(-) diff --git a/libs/@local/graph/postgres-store/src/store/postgres/query/expression/conditional.rs b/libs/@local/graph/postgres-store/src/store/postgres/query/expression/conditional.rs index 1fd54724601..82f64a23872 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/query/expression/conditional.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/query/expression/conditional.rs @@ -54,6 +54,11 @@ pub enum Function { /// Transpiles to `(extract(epoch from ) * 1000)::int8` in PostgreSQL. ExtractEpochMs(Box), Unnest(Vec), + /// Returns all subscript positions where the array element equals the given value. + /// + /// Transpiles to `array_positions(, )` in PostgreSQL. + /// Returns an integer array (e.g. `{1,3}`) or an empty array if no match. + ArrayPositions(Box, Box), Now, } @@ -196,6 +201,13 @@ impl Transpile for Function { fmt.write_char(')') } + Self::ArrayPositions(array, value) => { + fmt.write_str("array_positions(")?; + array.transpile(fmt)?; + fmt.write_str(", ")?; + value.transpile(fmt)?; + fmt.write_char(')') + } Self::JsonPathQueryFirst(target, path) => { fmt.write_str("jsonb_path_query_first(")?; target.transpile(fmt)?; @@ -271,6 +283,7 @@ pub enum PostgresType { BigInt, Boolean, TimestampTzRange, + Uuid, } impl Transpile for PostgresType { @@ -289,6 +302,7 @@ impl Transpile for PostgresType { Self::Int => fmt.write_str("int"), Self::BigInt => fmt.write_str("bigint"), Self::Boolean => fmt.write_str("boolean"), + Self::Uuid => fmt.write_str("uuid"), Self::TimestampTzRange => fmt.write_str("tstzrange"), } } diff --git a/libs/@local/hashql/eval/Cargo.toml b/libs/@local/hashql/eval/Cargo.toml index ae14bfb51e4..e4eae274a4f 100644 --- a/libs/@local/hashql/eval/Cargo.toml +++ b/libs/@local/hashql/eval/Cargo.toml @@ -22,6 +22,7 @@ hashql-core = { workspace = true } bytes = { workspace = true } futures-lite = { workspace = true } hash-graph-authorization = { workspace = true } +hash-graph-store = { workspace = true } postgres-protocol = { workspace = true } postgres-types = { workspace = true, features = ["uuid-1"] } serde = { workspace = true } diff --git a/libs/@local/hashql/eval/src/postgres/authorization.rs b/libs/@local/hashql/eval/src/postgres/authorization.rs index 307eefa8e56..1054db3cac3 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization.rs @@ -1,36 +1,174 @@ +//! Converts authorization policies into SQL conditions for entity queries. +//! +//! The compilation pipeline produces actor-agnostic queries. This module grafts +//! actor-specific policy conditions onto those queries at runtime, just before +//! the orchestrator executes them. +//! +//! The entry point is [`analysis_in`], which takes a compiled [`PreparedQuery`] +//! and a [`PolicyComponents`] and produces an [`AnalysisResidual`] containing: +//! +//! - A combined WHERE condition (the permit/forbid algebra) +//! - Auxiliary parameter values referenced by that condition +//! - The [`AuthorizationProjections`] that tracks which joins need to be appended + +use alloc::borrow::Cow; use core::alloc::Allocator; use hash_graph_authorization::policies::{ - Effect, PolicyComponents, + Effect, OptimizationData, PolicyComponents, action::ActionName, resource::{EntityResourceConstraint, EntityResourceFilter, ResourceConstraint}, }; use hash_graph_postgres_store::store::postgres::query::{ - Column, ColumnReference, Constant, Expression, TableReference, VariadicExpression, - VariadicOperator, table, + Alias, BinaryExpression, BinaryOperator, Column, ColumnReference, Constant, Expression, + ForeignKeyReference, FromItem, JoinType, PostgresType, Table, TableName, TableReference, table, }; -use hashql_core::id::{Id, bit_vec::FiniteBitSet}; +use hash_graph_store::filter::PathToken; use postgres_types::ToSql; +use type_system::{ + ontology::{BaseUrl, VersionedUrl}, + principal::actor::{ActorEntityUuid, ActorId}, +}; use super::{PreparedQuery, projections::Projections}; +/// Tracks joins that authorization conditions require beyond what the compiled +/// query already provides. +/// +/// Each accessor checks the base [`Projections`] first. If the table is already +/// joined in the compiled query, its alias is reused directly. Otherwise, a new +/// alias is allocated and the join is recorded for later compilation via +/// [`build_joins`](Self::build_joins). struct AuthorizationProjections<'base> { index: usize, base: &'base Projections, + + entity_editions: Option, + entity_is_of_type_ids: Option, } impl<'base> AuthorizationProjections<'base> { + const fn new(base: &'base Projections) -> Self { + Self { + index: base.index, + base, + entity_editions: None, + entity_is_of_type_ids: None, + } + } + + const fn next_alias(&mut self) -> Alias { + let alias = Alias { + condition_index: 0, + chain_depth: 0, + number: self.index, + }; + self.index += 1; + alias + } + fn temporal_metadata(&self) -> TableReference<'static> { self.base.temporal_metadata() } + + /// Returns a reference to `entity_editions`, reusing the base join when available. + /// + /// Used by [`CreatedByPrincipal`](EntityResourceFilter::CreatedByPrincipal) to access + /// the `provenance` column. + fn entity_editions(&mut self) -> TableReference<'static> { + let alias = if let Some(base_alias) = self.base.entity_editions { + base_alias + } else if let Some(alias) = self.entity_editions { + alias + } else { + let alias = self.next_alias(); + self.entity_editions = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityEditions), + alias: Some(alias), + } + } + + /// Returns a reference to `entity_is_of_type_ids`. + /// + /// Always allocates its own join. The base projections' `entity_type_ids` is a + /// LATERAL aggregate (UNNEST + jsonb_agg for the result set); its internal table + /// references are scoped to the subquery and not visible here. + fn entity_is_of_type_ids(&mut self) -> TableReference<'static> { + let alias = if let Some(alias) = self.entity_is_of_type_ids { + alias + } else { + let alias = self.next_alias(); + self.entity_is_of_type_ids = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityIsOfTypeIds), + alias: Some(alias), + } + } + + /// Appends authorization-specific joins to the FROM tree. + /// + /// Only produces joins for tables that were not already present in the base + /// projections. Tables reused from the base are referenced by their existing + /// alias and require no additional join. + fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { + if let Some(alias) = self.entity_editions { + let fk = ForeignKeyReference::Single { + on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), + join: Column::EntityEditions(table::EntityEditions::EditionId), + join_type: JoinType::Inner, + }; + + from = from + .join( + JoinType::Inner, + FromItem::table(Table::EntityEditions) + .alias(Table::EntityEditions.aliased(alias)), + ) + .on(fk.conditions(self.base.base_alias, alias)) + .build(); + } + + if let Some(alias) = self.entity_is_of_type_ids { + let fk = ForeignKeyReference::Single { + on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), + join: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::EntityEditionId), + join_type: JoinType::Inner, + }; + + from = from + .join( + JoinType::Inner, + FromItem::table(Table::EntityIsOfTypeIds) + .alias(Table::EntityIsOfTypeIds.aliased(alias)), + ) + .on(fk.conditions(self.base.base_alias, alias)) + .build(); + } + + from + } } +/// Accumulates runtime parameter values for authorization conditions. +/// +/// Compiled parameters occupy `$1..$K`. Auxiliary parameters start at `$K+1` +/// and are appended after compiled parameters during encoding. struct AuxiliaryParameters { initial_offset: usize, parameters: Vec, A>, } impl AuxiliaryParameters { + /// Pushes a value and returns its 1-based parameter index (`$N`). fn push(&mut self, value: impl ToSql + Sync + 'static) -> usize where A: Clone, @@ -42,99 +180,209 @@ impl AuxiliaryParameters { } } +/// Working state for converting policies into SQL expressions. struct PreparedAnalysis<'query, A: Allocator> { projections: AuthorizationProjections<'query>, parameters: AuxiliaryParameters, + actor_id: Option, } impl<'query, A: Allocator> PreparedAnalysis<'query, A> { - fn new_in(query: &'query PreparedQuery<'_, impl Allocator>, alloc: A) -> Self { + fn new_in( + query: &'query PreparedQuery<'_, impl Allocator>, + policy: &PolicyComponents, + alloc: A, + ) -> Self { Self { - projections: AuthorizationProjections { - index: query.projections.index, - base: &query.projections, - }, + projections: AuthorizationProjections::new(&query.projections), parameters: AuxiliaryParameters { initial_offset: query.parameters.len(), parameters: Vec::new_in(alloc), }, + actor_id: policy.actor_id(), } } } -struct AnalysisResidual { +/// The result of analyzing policies for a single query. +/// +/// Contains everything needed to graft authorization onto the compiled query: +/// a WHERE condition, the runtime parameter values it references, and the +/// projections that track which joins need to be appended to the FROM tree. +struct AnalysisResidual<'query, A: Allocator> { + /// Combined permit/forbid condition. condition: Expression, + /// Runtime values referenced by `condition` via `$N` parameter indices. + auxiliary_parameters: Vec, A>, + /// Tracks joins that need to be appended to the FROM tree. + projections: AuthorizationProjections<'query>, +} + +/// Checks whether the entity has a specific `(base_url, version)` type pair. +/// +/// Joins `entity_is_of_type_ids` (a view with parallel `base_urls text[]` and +/// `versions bigint[]` arrays). Uses `array_positions` to find all subscripts +/// where each value matches, then checks whether the two position sets overlap: +/// +/// ```sql +/// array_positions(eit.base_urls, $base::text) +/// && array_positions(eit.versions, $version::bigint) +/// ``` +/// +/// This preserves the pairing invariant: the overlap means there is some index `i` +/// where `base_urls[i] = $base AND versions[i] = $version`. +fn convert_is_of_type( + output: &mut PreparedAnalysis<'_, A>, + url: VersionedUrl, +) -> Expression { + let table = output.projections.entity_is_of_type_ids(); + + let base_url_index = output.parameters.push(url.base_url); + let version_index = output.parameters.push(url.version); + + // array_positions(table.base_urls, $base) + let base_url_positions = Expression::Function( + hash_graph_postgres_store::store::postgres::query::Function::ArrayPositions( + Box::new(Expression::ColumnReference(ColumnReference { + correlation: Some(table.clone()), + name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::BaseUrls).into(), + })), + Box::new(Expression::Parameter(base_url_index)), + ), + ); + + // array_positions(table.versions, $version) + let version_positions = Expression::Function( + hash_graph_postgres_store::store::postgres::query::Function::ArrayPositions( + Box::new(Expression::ColumnReference(ColumnReference { + correlation: Some(table), + name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::Versions).into(), + })), + Box::new(Expression::Parameter(version_index)), + ), + ); + + // array_positions(...) && array_positions(...) + // The overlap operator checks if the two integer arrays share any element, + // which means there is a position where both base_url and version match. + Expression::Binary(BinaryExpression { + op: BinaryOperator::Overlap, + left: Box::new(base_url_positions), + right: Box::new(version_positions), + }) +} + +/// Checks whether the entity has any type with the given base URL (any version). +/// +/// ```sql +/// $base = ANY(eit.base_urls) +/// ``` +fn convert_is_of_base_type( + output: &mut PreparedAnalysis<'_, A>, + base_url: BaseUrl, +) -> Expression { + let base_url_index = output.parameters.push(base_url); + + Expression::Binary(BinaryExpression { + op: BinaryOperator::In, + left: Box::new(Expression::Parameter(base_url_index)), + right: Box::new(Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.entity_is_of_type_ids()), + name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::BaseUrls).into(), + })), + }) +} + +/// Checks whether the entity was created by the current actor. +/// +/// Compares `entity_editions.provenance->>'createdById'` against the actor UUID. +/// For anonymous/public requests (no actor), compares against the public actor UUID. +fn convert_created_by_principal( + output: &mut PreparedAnalysis<'_, A>, +) -> Expression { + let actor_uuid = output + .actor_id + .map_or_else(ActorEntityUuid::public_actor, ActorEntityUuid::from); + let actor_index = output.parameters.push(actor_uuid); + + let provenance = Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.entity_editions()), + name: Column::EntityEditions(table::EntityEditions::Provenance).into(), + }); + + let created_by = Expression::Function( + hash_graph_postgres_store::store::postgres::query::Function::JsonExtractAsText( + Box::new(provenance), + PathToken::Field(Cow::Borrowed("createdById")), + ), + ); + + Expression::equal(created_by, Expression::Parameter(actor_index)) } +/// Converts an [`EntityResourceFilter`] tree into a SQL [`Expression`]. fn convert_entity_resource_filter( output: &mut PreparedAnalysis<'_, A>, filter: &EntityResourceFilter, ) -> Expression { match filter { - EntityResourceFilter::All { filters } => { - let expressions = filters + EntityResourceFilter::All { filters } => Expression::all( + filters .iter() .map(|filter| convert_entity_resource_filter(output, filter)) - .collect(); - - Expression::Variadic(VariadicExpression { - op: VariadicOperator::And, - exprs: expressions, - }) - } - EntityResourceFilter::Any { filters } => { - let expressions = filters + .collect(), + ), + EntityResourceFilter::Any { filters } => Expression::any( + filters .iter() .map(|filter| convert_entity_resource_filter(output, filter)) - .collect(); - - Expression::Variadic(VariadicExpression { - op: VariadicOperator::Or, - exprs: expressions, - }) - } + .collect(), + ), EntityResourceFilter::Not { filter } => { convert_entity_resource_filter(output, filter).not() } EntityResourceFilter::IsOfType { entity_type } => { - todo!() + convert_is_of_type(output, entity_type.clone()) } - EntityResourceFilter::IsOfBaseType { entity_type } => todo!(), - EntityResourceFilter::CreatedByPrincipal => { - todo!() + EntityResourceFilter::IsOfBaseType { entity_type } => { + convert_is_of_base_type(output, entity_type.clone()) } + EntityResourceFilter::CreatedByPrincipal => convert_created_by_principal(output), } } +/// Converts a [`ResourceConstraint`] into a SQL [`Expression`]. +/// +/// Non-entity resource types (entity types, property types, data types, meta) +/// produce `FALSE` since they cannot match entity rows. fn convert_resource_constraint( - output: &mut PreparedAnalysis, + output: &mut PreparedAnalysis<'_, A>, constraint: &ResourceConstraint, ) -> Expression { match constraint { &ResourceConstraint::Web { web_id } => { let index = output.parameters.push(web_id); - let reference = Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), - name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId).into(), - }); - - let param = Expression::Parameter(index); - - Expression::equal(reference, param) + Expression::equal( + Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId) + .into(), + }), + Expression::Parameter(index), + ) } &ResourceConstraint::Entity(EntityResourceConstraint::Exact { id }) => { let index = output.parameters.push(id); - let reference = Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), - name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) - .into(), - }); - - let param = Expression::Parameter(index); - - Expression::equal(reference, param) + Expression::equal( + Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) + .into(), + }), + Expression::Parameter(index), + ) } ResourceConstraint::Entity(EntityResourceConstraint::Web { web_id, filter }) => { let lhs = @@ -153,18 +401,90 @@ fn convert_resource_constraint( } } -fn analysis_in( - query: &PreparedQuery<'_, impl Allocator>, +fn optimize( + output: &mut PreparedAnalysis<'_, A>, + permits: &mut Option>, + OptimizationData { + permitted_entity_uuids, + permitted_entity_type_uuids: _, + permitted_property_type_uuids: _, + permitted_data_type_uuids: _, + permitted_web_ids, + }: &OptimizationData, +) { + match &**permitted_entity_uuids { + [] => {} + &[entity_uuid] => { + let index = output.parameters.push(entity_uuid); + + permits.get_or_insert_default().push(Expression::equal( + Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) + .into(), + }), + Expression::Parameter(index), + )); + } + entity_uuids => { + let index = output.parameters.push(entity_uuids.to_vec()); + + permits.get_or_insert_default().push(Expression::r#in( + Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) + .into(), + }), + Expression::Parameter(index) + .cast(PostgresType::Array(Box::new(PostgresType::Uuid))), + )); + } + } + + match &**permitted_web_ids { + [] => {} + &[web_id] => { + let index = output.parameters.push(web_id); + + permits.get_or_insert_default().push(Expression::equal( + Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId) + .into(), + }), + Expression::Parameter(index), + )); + } + web_ids => { + let index = output.parameters.push(web_ids.to_vec()); + + permits.get_or_insert_default().push(Expression::r#in( + Expression::ColumnReference(ColumnReference { + correlation: Some(output.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId) + .into(), + }), + Expression::Parameter(index) + .cast(PostgresType::Array(Box::new(PostgresType::Uuid))), + )); + } + } +} + +fn analysis_in<'query, A: Allocator + Clone>( + query: &'query PreparedQuery<'_, impl Allocator>, policy: &PolicyComponents, alloc: A, -) -> AnalysisResidual { - let policies = policy.extract_filter_policies(match query.vertex_type { +) -> AnalysisResidual<'query, A> { + let action = match query.vertex_type { hashql_mir::pass::execution::VertexType::Entity => ActionName::ViewEntity, - }); + }; + let policies = policy.extract_filter_policies(action); + let optimization_data = policy.optimization_data(action); - let mut output = PreparedAnalysis::new_in(query, alloc); - let mut permits = Vec::new(); - let mut forbids = Vec::new(); + let mut output = PreparedAnalysis::new_in(query, policy, alloc); + let mut permits: Option> = None; + let mut forbids: Option> = None; let mut blank_permit = false; for (effect, constraint) in policies { @@ -172,18 +492,45 @@ fn analysis_in( (Effect::Permit, _) if blank_permit => {} (Effect::Permit, None) => blank_permit = true, (Effect::Forbid, None) => { + // Blank forbid: deny everything, no further analysis needed. + return AnalysisResidual { condition: Expression::Constant(Constant::Boolean(false)), + auxiliary_parameters: Vec::new_in( + output.parameters.parameters.allocator().clone(), + ), + projections: AuthorizationProjections::new(output.projections.base), }; } (Effect::Permit, Some(constraint)) => { - permits.push(convert_resource_constraint(&mut output, constraint)); + permits + .get_or_insert_default() + .push(convert_resource_constraint(&mut output, constraint)); } (Effect::Forbid, Some(constraint)) => { - forbids.push(convert_resource_constraint(&mut output, constraint)); + forbids + .get_or_insert_default() + .push(convert_resource_constraint(&mut output, constraint)); } } } - todo!() + optimize(&mut output, &mut permits, optimization_data); + + let permits = permits.map(Expression::any); + let forbids = forbids.map(Expression::any); + + let expression = match (blank_permit, permits, forbids) { + (true, _, None) => Expression::Constant(Constant::Boolean(true)), + (true, _, Some(forbids)) => forbids.not(), + (false, None, _) => Expression::Constant(Constant::Boolean(false)), + (false, Some(permits), None) => permits, + (false, Some(permits), Some(forbids)) => Expression::all(vec![permits, forbids.not()]), + }; + + AnalysisResidual { + condition: expression, + auxiliary_parameters: output.parameters.parameters, + projections: output.projections, + } } From 28aa21809fcebf15c27cced4d1f468e4071588ea Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:58:44 +0200 Subject: [PATCH 07/34] feat: checkpoint --- libs/@local/hashql/eval/AUTHORIZATION.md | 134 ++++++++++++------ .../hashql/eval/src/postgres/authorization.rs | 61 +++++--- .../hashql/eval/src/postgres/projections.rs | 12 +- 3 files changed, 139 insertions(+), 68 deletions(-) diff --git a/libs/@local/hashql/eval/AUTHORIZATION.md b/libs/@local/hashql/eval/AUTHORIZATION.md index a096d58a11b..94344931a5d 100644 --- a/libs/@local/hashql/eval/AUTHORIZATION.md +++ b/libs/@local/hashql/eval/AUTHORIZATION.md @@ -61,16 +61,24 @@ in the graft phase. ### Condition shapes -All admission conditions are top-level WHERE predicates or correlated EXISTS -subqueries. No FROM tree mutation for admission, no alias allocation. - -| Policy condition | SQL shape | -| ------------------------- | -------------------------------------------------------- | -| Entity UUID permit/forbid | `.entity_uuid = $N` or `= ANY($N::uuid[])` | -| Web ID permit/forbid | `.web_id = $N` or `= ANY($N::uuid[])` | -| CreatedByPrincipal | Correlated EXISTS on `entity_editions` | -| IsOfType | Correlated EXISTS on `entity_is_of_type_ids` with UNNEST | -| IsOfBaseType | Correlated EXISTS on `entity_is_of_type_ids` | +Entity UUID and web ID conditions are plain WHERE predicates on the base +table. IsOfType and IsOfBaseType use INNER JOINs on `entity_is_of_type_ids` +with `array_positions` for paired checks. CreatedByPrincipal checks +provenance on the joined `entity_editions`. + +INNER JOINs are safe here because both `entity_is_of_type_ids` and +`entity_editions` have total coverage: every entity in +`entity_temporal_metadata` has corresponding rows in both tables. This makes +INNER JOIN semantically equivalent to EXISTS in all Boolean contexts +(including under OR and NOT). + +| Policy condition | SQL shape | +| ------------------------- | ------------------------------------------------------------------------- | +| Entity UUID permit/forbid | `.entity_uuid = $N` or `= ANY($N::uuid[])` | +| Web ID permit/forbid | `.web_id = $N` or `= ANY($N::uuid[])` | +| CreatedByPrincipal | `ee.provenance->>'createdById' = $N::text` | +| IsOfType | `array_positions(eit.base_urls, $N) && array_positions(eit.versions, $M)` | +| IsOfBaseType | `$N = ANY(eit.base_urls)` | ### Base table conditions @@ -78,62 +86,63 @@ Entity UUID and web ID reference columns on the base table (`entity_temporal_metadata`). These are plain WHERE predicates: ```sql --- permits (OR'd) +-- single value +.entity_uuid = $N +.web_id = $N + +-- batched (from OptimizationData) .entity_uuid = ANY($N::uuid[]) .web_id = ANY($N::uuid[]) - --- forbids (negated) -NOT (.entity_uuid = ANY($N::uuid[])) ``` ### CreatedByPrincipal -Provenance lives on `entity_editions`. Always expressed as a correlated EXISTS, -regardless of whether entity_editions is joined in the compiled query. -PostgreSQL may optimize the correlated EXISTS into a semijoin that shares -work with an existing join, though this is not guaranteed. +Checks the entity's provenance on `entity_ids` (not `entity_editions`). +The existing `Filter::for_resource_filter` uses `EntityQueryPath::Provenance` +which maps to `Relation::EntityIds`. This is the entity-level provenance +(who created the entity originally), not the edition-level provenance +(who created each specific edition). + +The authorization projections join `entity_ids` if it is not already +joined by the compiled query (punch-through via `AuthorizationProjections`). +The join is on `(web_id, entity_uuid)`: ```sql -EXISTS ( - SELECT 1 - FROM entity_editions ee_auth - WHERE ee_auth.entity_edition_id = .entity_edition_id - AND ee_auth.provenance->>'createdById' = $N::text -) +ids.provenance->>'createdById' = $N::text ``` -The actor ID uses `ActorEntityUuid::public_actor()` for anonymous actors, -matching the existing `for_resource_filter` behavior. +The actor UUID parameter is pushed as a UUID and cast to `text` in SQL +(via `.cast(PostgresType::Text)`), not converted to a string in Rust. +Anonymous actors use `ActorEntityUuid::public_actor()`, matching the +existing `for_resource_filter` behavior. ### IsOfType (paired arrays) `entity_is_of_type_ids` stores `base_urls` (`text[]`) and `versions` (`bigint[]`) as parallel arrays. Independent overlap on the two arrays loses the pairing invariant (stored pairs `(A,1),(B,2)` would falsely match query -`(A,2)`). Correct form uses UNNEST: +`(A,2)`). `array_positions` preserves pairing by finding overlapping +subscript positions: ```sql -EXISTS ( - SELECT 1 - FROM entity_is_of_type_ids eit - CROSS JOIN LATERAL UNNEST(eit.base_urls, eit.versions) AS u(b, v) - WHERE eit.entity_edition_id = .entity_edition_id - AND u.b = $N_base::text - AND u.v = $N_version::int8 -) +array_positions(eit.base_urls, $N_base::text) +&& array_positions(eit.versions, $N_version::bigint) ``` +`array_positions(arr, val)` returns all subscripts where the element matches. +The `&&` operator checks whether the two integer arrays share any element. +A shared position means `base_urls[i] = $base AND versions[i] = $version`. + +The `entity_is_of_type_ids` table is joined via `AuthorizationProjections`, +which always allocates its own join (the compiled query's LATERAL aggregate +serves a different purpose). + ### IsOfBaseType -Base URL alone is sufficient; no UNNEST needed: +Base URL alone is sufficient; no pairing needed: ```sql -EXISTS ( - SELECT 1 - FROM entity_is_of_type_ids eit - WHERE eit.entity_edition_id = .entity_edition_id - AND $N_base::text = ANY(eit.base_urls) -) +$N_base = ANY(eit.base_urls) ``` ### Combination algebra @@ -149,6 +158,26 @@ permits + forbids -> WHERE (permits) AND NOT (forbids) no permits -> WHERE FALSE (deny all) ``` +### Two-phase allocation + +Auxiliary parameters must not be allocated speculatively. Every parameter +pushed must be referenced by an expression in the final SQL. Dead parameters +cause bind-arity mismatches at runtime. + +The graft uses a two-phase approach: + +1. **Normalize**: walk policies, collect constraint references and determine + the algebra (blank permit, blank forbid, constrained permits/forbids). + No SQL expressions or parameters are allocated. + +2. **Lower**: convert only the surviving constraints to SQL expressions and + push their parameter values. If blank_permit is true, only forbids are + lowered. If no permits survive, return FALSE with empty parameters. + +The optimization pass (`OptimizationData`) is also conditional: batched +permit UUIDs and web IDs are only lowered if permits are live (i.e. +`blank_permit` is false). + ### Instance admin bypass When `policy_components.is_instance_admin()` is true, no admission conditions @@ -184,10 +213,10 @@ and there is nothing to mask. ### Graft: expression replacement -The graft locates the entity_editions subquery in the FROM tree using -`JoinMetadata::entity_editions`. It scans the subquery's SELECT list for -entries with alias `properties` and `property_metadata`, and replaces their -inner expressions: +The graft locates the entity_editions subquery in the FROM tree by matching +the alias from `Projections::entity_editions`. It scans the subquery's SELECT +list for entries with alias `properties` and `property_metadata`, and replaces +their inner expressions: ``` Before: properties AS properties @@ -208,6 +237,19 @@ The alias is unchanged, so all downstream references (`ee.properties`, filter expressions using `ee.properties->>'path'`) transparently get the masked version. +### Scope of replacement expressions + +The replacement expression lives inside the entity_editions subquery. It can +reference: + +- Columns from the inner source alias (e.g. `ee_src.entity_edition_id`) +- Constants and parameter placeholders +- Correlated subqueries keyed on `ee_src.entity_edition_id` + +It cannot reference outer aliases (e.g. `entity_temporal_metadata`) unless +the subquery is made LATERAL. For type-dependent masks, use correlated +subqueries inside the replacement expression, not references to outer joins. + ### Composability This approach is general. The subquery form is always present when diff --git a/libs/@local/hashql/eval/src/postgres/authorization.rs b/libs/@local/hashql/eval/src/postgres/authorization.rs index 1054db3cac3..bd6214ae847 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization.rs @@ -43,6 +43,7 @@ struct AuthorizationProjections<'base> { index: usize, base: &'base Projections, + entity_ids: Option, entity_editions: Option, entity_is_of_type_ids: Option, } @@ -52,6 +53,7 @@ impl<'base> AuthorizationProjections<'base> { Self { index: base.index, base, + entity_ids: None, entity_editions: None, entity_is_of_type_ids: None, } @@ -71,10 +73,30 @@ impl<'base> AuthorizationProjections<'base> { self.base.temporal_metadata() } - /// Returns a reference to `entity_editions`, reusing the base join when available. + /// Returns a reference to `entity_ids`, reusing the base join when available. /// /// Used by [`CreatedByPrincipal`](EntityResourceFilter::CreatedByPrincipal) to access - /// the `provenance` column. + /// entity-level provenance (who created the entity originally). + /// Joins on `(web_id, entity_uuid)`. + fn entity_ids(&mut self) -> TableReference<'static> { + let alias = if let Some(base_alias) = self.base.entity_ids { + base_alias + } else if let Some(alias) = self.entity_ids { + alias + } else { + let alias = self.next_alias(); + self.entity_ids = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityIds), + alias: Some(alias), + } + } + + /// Returns a reference to `entity_editions`, reusing the base join when available. fn entity_editions(&mut self) -> TableReference<'static> { let alias = if let Some(base_alias) = self.base.entity_editions { base_alias @@ -120,21 +142,12 @@ impl<'base> AuthorizationProjections<'base> { /// projections. Tables reused from the base are referenced by their existing /// alias and require no additional join. fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { - if let Some(alias) = self.entity_editions { - let fk = ForeignKeyReference::Single { - on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), - join: Column::EntityEditions(table::EntityEditions::EditionId), - join_type: JoinType::Inner, - }; + if let Some(alias) = self.entity_ids { + from = self.base.build_entity_ids(from, alias); + } - from = from - .join( - JoinType::Inner, - FromItem::table(Table::EntityEditions) - .alias(Table::EntityEditions.aliased(alias)), - ) - .on(fk.conditions(self.base.base_alias, alias)) - .build(); + if let Some(alias) = self.entity_editions { + from = self.base.build_entity_editions(from, alias); } if let Some(alias) = self.entity_is_of_type_ids { @@ -295,7 +308,12 @@ fn convert_is_of_base_type( /// Checks whether the entity was created by the current actor. /// -/// Compares `entity_editions.provenance->>'createdById'` against the actor UUID. +/// Compares `entity_ids.provenance->>'createdById'` against the actor UUID. +/// This is entity-level provenance (who created the entity originally), not +/// edition-level provenance (who created each specific edition). Matches +/// the existing `EntityQueryPath::Provenance` which maps to `entity_ids`. +/// +/// The actor UUID is cast to `text` in SQL since `->>'createdById'` returns text. /// For anonymous/public requests (no actor), compares against the public actor UUID. fn convert_created_by_principal( output: &mut PreparedAnalysis<'_, A>, @@ -306,8 +324,8 @@ fn convert_created_by_principal( let actor_index = output.parameters.push(actor_uuid); let provenance = Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.entity_editions()), - name: Column::EntityEditions(table::EntityEditions::Provenance).into(), + correlation: Some(output.projections.entity_ids()), + name: Column::EntityIds(table::EntityIds::Provenance).into(), }); let created_by = Expression::Function( @@ -317,7 +335,10 @@ fn convert_created_by_principal( ), ); - Expression::equal(created_by, Expression::Parameter(actor_index)) + Expression::equal( + created_by, + Expression::Parameter(actor_index).cast(PostgresType::Text), + ) } /// Converts an [`EntityResourceFilter`] tree into a SQL [`Expression`]. diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index 80b99c5ae97..3e87a066169 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -215,7 +215,11 @@ impl Projections { from } - fn build_entity_editions<'item>(&self, from: FromItem<'item>, alias: Alias) -> FromItem<'item> { + pub(crate) fn build_entity_editions<'item>( + &self, + from: FromItem<'item>, + alias: Alias, + ) -> FromItem<'item> { let fk = ForeignKeyReference::Single { on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), join: Column::EntityEditions(table::EntityEditions::EditionId), @@ -230,7 +234,11 @@ impl Projections { .build() } - fn build_entity_ids<'item>(&self, from: FromItem<'item>, alias: Alias) -> FromItem<'item> { + pub(crate) fn build_entity_ids<'item>( + &self, + from: FromItem<'item>, + alias: Alias, + ) -> FromItem<'item> { let fk = ForeignKeyReference::Double { on: [ Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId), From be100d5c5db3f13ed2418c43f10ba3389a67923e Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:28:02 +0200 Subject: [PATCH 08/34] feat: do not skip parameters --- .../hashql/eval/src/postgres/authorization.rs | 142 +++++++++--------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/libs/@local/hashql/eval/src/postgres/authorization.rs b/libs/@local/hashql/eval/src/postgres/authorization.rs index bd6214ae847..a6e033e1f83 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization.rs @@ -32,13 +32,10 @@ use type_system::{ use super::{PreparedQuery, projections::Projections}; -/// Tracks joins that authorization conditions require beyond what the compiled -/// query already provides. +/// Tracks joins that authorization conditions need. /// -/// Each accessor checks the base [`Projections`] first. If the table is already -/// joined in the compiled query, its alias is reused directly. Otherwise, a new -/// alias is allocated and the join is recorded for later compilation via -/// [`build_joins`](Self::build_joins). +/// Accessors reuse joins from the base [`Projections`] when available, falling +/// back to fresh joins compiled by [`build_joins`](Self::build_joins). struct AuthorizationProjections<'base> { index: usize, base: &'base Projections, @@ -73,11 +70,7 @@ impl<'base> AuthorizationProjections<'base> { self.base.temporal_metadata() } - /// Returns a reference to `entity_ids`, reusing the base join when available. - /// - /// Used by [`CreatedByPrincipal`](EntityResourceFilter::CreatedByPrincipal) to access - /// entity-level provenance (who created the entity originally). - /// Joins on `(web_id, entity_uuid)`. + /// Entity-level provenance, joined on `(web_id, entity_uuid)`. fn entity_ids(&mut self) -> TableReference<'static> { let alias = if let Some(base_alias) = self.base.entity_ids { base_alias @@ -96,7 +89,7 @@ impl<'base> AuthorizationProjections<'base> { } } - /// Returns a reference to `entity_editions`, reusing the base join when available. + /// Entity edition data, joined on `edition_id`. fn entity_editions(&mut self) -> TableReference<'static> { let alias = if let Some(base_alias) = self.base.entity_editions { base_alias @@ -115,11 +108,10 @@ impl<'base> AuthorizationProjections<'base> { } } - /// Returns a reference to `entity_is_of_type_ids`. + /// Entity type assignments, joined on `entity_edition_id`. /// - /// Always allocates its own join. The base projections' `entity_type_ids` is a - /// LATERAL aggregate (UNNEST + jsonb_agg for the result set); its internal table - /// references are scoped to the subquery and not visible here. + /// Always allocates a fresh join; the base projections' type aggregate + /// is a scoped LATERAL subquery and cannot be reused. fn entity_is_of_type_ids(&mut self) -> TableReference<'static> { let alias = if let Some(alias) = self.entity_is_of_type_ids { alias @@ -136,11 +128,7 @@ impl<'base> AuthorizationProjections<'base> { } } - /// Appends authorization-specific joins to the FROM tree. - /// - /// Only produces joins for tables that were not already present in the base - /// projections. Tables reused from the base are referenced by their existing - /// alias and require no additional join. + /// Appends joins for tables not already present in the base projections. fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { if let Some(alias) = self.entity_ids { from = self.base.build_entity_ids(from, alias); @@ -171,10 +159,8 @@ impl<'base> AuthorizationProjections<'base> { } } -/// Accumulates runtime parameter values for authorization conditions. -/// -/// Compiled parameters occupy `$1..$K`. Auxiliary parameters start at `$K+1` -/// and are appended after compiled parameters during encoding. +/// Runtime parameter values for authorization conditions, indexed after +/// the compiled parameters (`$K+1..`). struct AuxiliaryParameters { initial_offset: usize, parameters: Vec, A>, @@ -193,7 +179,7 @@ impl AuxiliaryParameters { } } -/// Working state for converting policies into SQL expressions. +/// Context for policy-to-SQL conversion. struct PreparedAnalysis<'query, A: Allocator> { projections: AuthorizationProjections<'query>, parameters: AuxiliaryParameters, @@ -217,33 +203,23 @@ impl<'query, A: Allocator> PreparedAnalysis<'query, A> { } } -/// The result of analyzing policies for a single query. -/// -/// Contains everything needed to graft authorization onto the compiled query: -/// a WHERE condition, the runtime parameter values it references, and the -/// projections that track which joins need to be appended to the FROM tree. +/// Everything needed to graft authorization onto a compiled query. struct AnalysisResidual<'query, A: Allocator> { - /// Combined permit/forbid condition. condition: Expression, - /// Runtime values referenced by `condition` via `$N` parameter indices. + /// Parameter values referenced by `condition` via `$N` indices. auxiliary_parameters: Vec, A>, - /// Tracks joins that need to be appended to the FROM tree. projections: AuthorizationProjections<'query>, } /// Checks whether the entity has a specific `(base_url, version)` type pair. /// -/// Joins `entity_is_of_type_ids` (a view with parallel `base_urls text[]` and -/// `versions bigint[]` arrays). Uses `array_positions` to find all subscripts -/// where each value matches, then checks whether the two position sets overlap: -/// /// ```sql /// array_positions(eit.base_urls, $base::text) /// && array_positions(eit.versions, $version::bigint) /// ``` /// -/// This preserves the pairing invariant: the overlap means there is some index `i` -/// where `base_urls[i] = $base AND versions[i] = $version`. +/// The overlap of position sets preserves the pairing invariant: a shared +/// position means `base_urls[i] = $base AND versions[i] = $version`. fn convert_is_of_type( output: &mut PreparedAnalysis<'_, A>, url: VersionedUrl, @@ -253,7 +229,7 @@ fn convert_is_of_type( let base_url_index = output.parameters.push(url.base_url); let version_index = output.parameters.push(url.version); - // array_positions(table.base_urls, $base) + // array_positions(eit.base_urls, $base) let base_url_positions = Expression::Function( hash_graph_postgres_store::store::postgres::query::Function::ArrayPositions( Box::new(Expression::ColumnReference(ColumnReference { @@ -264,7 +240,7 @@ fn convert_is_of_type( ), ); - // array_positions(table.versions, $version) + // array_positions(eit.versions, $version) let version_positions = Expression::Function( hash_graph_postgres_store::store::postgres::query::Function::ArrayPositions( Box::new(Expression::ColumnReference(ColumnReference { @@ -276,8 +252,6 @@ fn convert_is_of_type( ); // array_positions(...) && array_positions(...) - // The overlap operator checks if the two integer arrays share any element, - // which means there is a position where both base_url and version match. Expression::Binary(BinaryExpression { op: BinaryOperator::Overlap, left: Box::new(base_url_positions), @@ -296,6 +270,7 @@ fn convert_is_of_base_type( ) -> Expression { let base_url_index = output.parameters.push(base_url); + // $base = ANY(eit.base_urls) Expression::Binary(BinaryExpression { op: BinaryOperator::In, left: Box::new(Expression::Parameter(base_url_index)), @@ -308,13 +283,8 @@ fn convert_is_of_base_type( /// Checks whether the entity was created by the current actor. /// -/// Compares `entity_ids.provenance->>'createdById'` against the actor UUID. -/// This is entity-level provenance (who created the entity originally), not -/// edition-level provenance (who created each specific edition). Matches -/// the existing `EntityQueryPath::Provenance` which maps to `entity_ids`. -/// -/// The actor UUID is cast to `text` in SQL since `->>'createdById'` returns text. -/// For anonymous/public requests (no actor), compares against the public actor UUID. +/// Uses entity-level provenance from `entity_ids` (the original creator). +/// Anonymous requests compare against the public actor UUID. fn convert_created_by_principal( output: &mut PreparedAnalysis<'_, A>, ) -> Expression { @@ -323,6 +293,7 @@ fn convert_created_by_principal( .map_or_else(ActorEntityUuid::public_actor, ActorEntityUuid::from); let actor_index = output.parameters.push(actor_uuid); + // ids.provenance->>'createdById' let provenance = Expression::ColumnReference(ColumnReference { correlation: Some(output.projections.entity_ids()), name: Column::EntityIds(table::EntityIds::Provenance).into(), @@ -335,6 +306,7 @@ fn convert_created_by_principal( ), ); + // ->> returns text, so the UUID parameter needs a text cast Expression::equal( created_by, Expression::Parameter(actor_index).cast(PostgresType::Text), @@ -384,6 +356,7 @@ fn convert_resource_constraint( &ResourceConstraint::Web { web_id } => { let index = output.parameters.push(web_id); + // base.web_id = $N Expression::equal( Expression::ColumnReference(ColumnReference { correlation: Some(output.projections.temporal_metadata()), @@ -396,6 +369,7 @@ fn convert_resource_constraint( &ResourceConstraint::Entity(EntityResourceConstraint::Exact { id }) => { let index = output.parameters.push(id); + // base.entity_uuid = $N Expression::equal( Expression::ColumnReference(ColumnReference { correlation: Some(output.projections.temporal_metadata()), @@ -422,6 +396,7 @@ fn convert_resource_constraint( } } +/// Adds batched permit expressions from pre-analyzed [`OptimizationData`]. fn optimize( output: &mut PreparedAnalysis<'_, A>, permits: &mut Option>, @@ -438,6 +413,7 @@ fn optimize( &[entity_uuid] => { let index = output.parameters.push(entity_uuid); + // base.entity_uuid = $N permits.get_or_insert_default().push(Expression::equal( Expression::ColumnReference(ColumnReference { correlation: Some(output.projections.temporal_metadata()), @@ -450,6 +426,7 @@ fn optimize( entity_uuids => { let index = output.parameters.push(entity_uuids.to_vec()); + // base.entity_uuid = ANY($N::uuid[]) permits.get_or_insert_default().push(Expression::r#in( Expression::ColumnReference(ColumnReference { correlation: Some(output.projections.temporal_metadata()), @@ -467,6 +444,7 @@ fn optimize( &[web_id] => { let index = output.parameters.push(web_id); + // base.web_id = $N permits.get_or_insert_default().push(Expression::equal( Expression::ColumnReference(ColumnReference { correlation: Some(output.projections.temporal_metadata()), @@ -479,6 +457,7 @@ fn optimize( web_ids => { let index = output.parameters.push(web_ids.to_vec()); + // base.web_id = ANY($N::uuid[]) permits.get_or_insert_default().push(Expression::r#in( Expression::ColumnReference(ColumnReference { correlation: Some(output.projections.temporal_metadata()), @@ -492,10 +471,14 @@ fn optimize( } } -fn analysis_in<'query, A: Allocator + Clone>( +/// Converts policies into a SQL condition using the permit/forbid algebra. +/// +/// `scratch` is used for temporary constraint collection. +fn analysis_in<'query, A: Allocator + Clone, S: Allocator>( query: &'query PreparedQuery<'_, impl Allocator>, policy: &PolicyComponents, alloc: A, + scratch: S, ) -> AnalysisResidual<'query, A> { let action = match query.vertex_type { hashql_mir::pass::execution::VertexType::Entity => ActionName::ViewEntity, @@ -503,49 +486,70 @@ fn analysis_in<'query, A: Allocator + Clone>( let policies = policy.extract_filter_policies(action); let optimization_data = policy.optimization_data(action); - let mut output = PreparedAnalysis::new_in(query, policy, alloc); - let mut permits: Option> = None; - let mut forbids: Option> = None; + let mut permit_constraints = Vec::new_in(&scratch); + let mut forbid_constraints = Vec::new_in(&scratch); let mut blank_permit = false; for (effect, constraint) in policies { match (effect, constraint) { (Effect::Permit, _) if blank_permit => {} - (Effect::Permit, None) => blank_permit = true, + (Effect::Permit, None) => { + blank_permit = true; + permit_constraints.clear(); + } (Effect::Forbid, None) => { // Blank forbid: deny everything, no further analysis needed. - return AnalysisResidual { condition: Expression::Constant(Constant::Boolean(false)), - auxiliary_parameters: Vec::new_in( - output.parameters.parameters.allocator().clone(), - ), - projections: AuthorizationProjections::new(output.projections.base), + auxiliary_parameters: Vec::new_in(alloc), + projections: AuthorizationProjections::new(&query.projections), }; } (Effect::Permit, Some(constraint)) => { - permits - .get_or_insert_default() - .push(convert_resource_constraint(&mut output, constraint)); + permit_constraints.push(constraint); } (Effect::Forbid, Some(constraint)) => { - forbids - .get_or_insert_default() - .push(convert_resource_constraint(&mut output, constraint)); + forbid_constraints.push(constraint); } } } - optimize(&mut output, &mut permits, optimization_data); + // Phase 2: lower only surviving constraints to SQL + let mut output = PreparedAnalysis::new_in(query, policy, alloc); + let mut permits = Some(permit_constraints) + .filter(|constraints| !constraints.is_empty()) + .map(|constraints| { + constraints + .into_iter() + .map(|constraint| convert_resource_constraint(&mut output, constraint)) + .collect() + }); + if !blank_permit { + optimize(&mut output, &mut permits, optimization_data); + } let permits = permits.map(Expression::any); - let forbids = forbids.map(Expression::any); + + let forbids = Some(forbid_constraints) + .filter(|constraints| !constraints.is_empty()) + .map(|constraints| { + constraints + .into_iter() + .map(|constraint| convert_resource_constraint(&mut output, constraint)) + .collect() + }) + .map(Expression::any); let expression = match (blank_permit, permits, forbids) { + // blank permit, no forbids: allow all (true, _, None) => Expression::Constant(Constant::Boolean(true)), + // blank permit + forbids: allow everything except forbidden (true, _, Some(forbids)) => forbids.not(), + // no permits at all: deny all (false, None, _) => Expression::Constant(Constant::Boolean(false)), + // constrained permits only (false, Some(permits), None) => permits, + // constrained permits + forbids (false, Some(permits), Some(forbids)) => Expression::all(vec![permits, forbids.not()]), }; From f2dd3b1f42e1ad5f53d1c58cdaf96899ddf538f8 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:43:09 +0200 Subject: [PATCH 09/34] feat: remove property masking in favour of lateral join --- libs/@local/graph/postgres-store/src/lib.rs | 2 + .../src/store/postgres/query/table.rs | 23 +++++ .../hashql/eval/src/postgres/filter/tests.rs | 53 +---------- libs/@local/hashql/eval/src/postgres/mod.rs | 31 +----- .../hashql/eval/src/postgres/projections.rs | 94 ++++++++++++++++--- 5 files changed, 112 insertions(+), 91 deletions(-) diff --git a/libs/@local/graph/postgres-store/src/lib.rs b/libs/@local/graph/postgres-store/src/lib.rs index 4fc05125e87..e582f1fbbe6 100644 --- a/libs/@local/graph/postgres-store/src/lib.rs +++ b/libs/@local/graph/postgres-store/src/lib.rs @@ -10,6 +10,8 @@ // Library Features extend_one, iter_intersperse, + variant_count, + maybe_uninit_array_assume_init )] #![cfg_attr(not(miri), doc(test(attr(deny(warnings, clippy::all)))))] #![expect( diff --git a/libs/@local/graph/postgres-store/src/store/postgres/query/table.rs b/libs/@local/graph/postgres-store/src/store/postgres/query/table.rs index 98fa7a37781..bf1936a7e2b 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/query/table.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/query/table.rs @@ -2,6 +2,7 @@ use core::{ fmt::{self, Debug, Formatter}, hash::Hash, iter::{Chain, Once, once}, + mem::MaybeUninit, }; use hash_graph_store::{ @@ -1039,6 +1040,7 @@ impl DatabaseColumn for EntityEmbeddings { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[repr(u8)] pub enum EntityEditions { EditionId, Properties, @@ -1048,6 +1050,27 @@ pub enum EntityEditions { PropertyMetadata, } +impl EntityEditions { + #[expect(unsafe_code, clippy::cast_possible_truncation)] + pub const ALL: [Self; core::mem::variant_count::()] = { + let mut output = [MaybeUninit::uninit(); core::mem::variant_count::()]; + + let mut index = 0; + while index < core::mem::variant_count::() { + // SAFETY: `Self` is `repr(u8)` with contiguous discriminants starting at 0 (no explicit + // discriminant values). `index` ranges over `0..variant_count`, so every value is a + // valid discriminant. + let variant = unsafe { core::mem::transmute::(index as u8) }; + + output[index].write(variant); + index += 1; + } + + // SAFETY: We have initialized all variants in the loop above. + unsafe { MaybeUninit::array_assume_init(output) } + }; +} + impl DatabaseColumn for EntityEditions { fn parameter_type(self) -> ParameterType { match self { diff --git a/libs/@local/hashql/eval/src/postgres/filter/tests.rs b/libs/@local/hashql/eval/src/postgres/filter/tests.rs index 486ec2b8707..94dff504fb5 100644 --- a/libs/@local/hashql/eval/src/postgres/filter/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/filter/tests.rs @@ -11,7 +11,7 @@ use alloc::alloc::Global; use std::path::PathBuf; -use hash_graph_postgres_store::store::postgres::query::{Expression, Transpile as _}; +use hash_graph_postgres_store::store::postgres::query::Transpile as _; use hashql_core::{ heap::{Heap, Scratch}, id::Id as _, @@ -269,14 +269,6 @@ impl core::fmt::Display for QueryReport { } fn compile_full_query<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> QueryReport { - compile_full_query_with_mask(fixture, heap, None) -} - -fn compile_full_query_with_mask<'heap>( - fixture: &Fixture<'heap>, - heap: &'heap Heap, - property_mask: Option, -) -> QueryReport { let mut scratch = Scratch::new(); let def = fixture.def(); @@ -304,8 +296,7 @@ fn compile_full_query_with_mask<'heap>( }; let prepared_query = { - let mut compiler = - PostgresCompiler::new_in(&mut context, &mut scratch).with_property_mask(property_mask); + let mut compiler = PostgresCompiler::new_in(&mut context, &mut scratch); compiler.compile_graph_read(&read) }; @@ -793,46 +784,6 @@ fn left_entity_filter() { assert_snapshot!("left_entity_filter", report.to_string()); } -/// Property mask wraps `properties` and `property_metadata` SELECT expressions with -/// `(col - mask)` but leaves other columns untouched. -#[test] -fn property_mask() { - let heap = Heap::new(); - let interner = Interner::new(&heap); - let env = Environment::new(&heap); - - let callee_id = DefId::new(99); - - // Properties access in bb0 (Postgres Data island) with an apply in bb1 (Interpreter) - // ensures Properties and `PropertyMetadata` appear in the provides set. - let body = body!(interner, env; [graph::read::filter]@0/2 -> ? { - decl env: (), vertex: (|t| entity_types::types::entity(t, t.unknown(), None)), - props: ?, prop_meta: ?, func: [fn() -> ?], result: ?; - @proj v_props = vertex.properties: ?, - v_meta = vertex.metadata: ?, - v_prop_meta = v_meta.property_metadata: ?; - - bb0() { - props = load v_props; - prop_meta = load v_prop_meta; - func = load callee_id; - result = apply func; - return result; - } - }); - - let fixture = Fixture::new(&heap, env, body); - - // Use a parameter placeholder as the mask expression. - let mask = Expression::Parameter(99); - - let report = compile_full_query_with_mask(&fixture, &heap, Some(mask)); - - let settings = snapshot_settings(); - let _guard = settings.bind_to_scope(); - assert_snapshot!("property_mask", report.to_string()); -} - /// Tuple aggregate followed by `.0` numeric field projection → /// `json_extract_path(base, (0)::text)`. #[test] diff --git a/libs/@local/hashql/eval/src/postgres/mod.rs b/libs/@local/hashql/eval/src/postgres/mod.rs index 3257df634b3..90beb53f7c1 100644 --- a/libs/@local/hashql/eval/src/postgres/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/mod.rs @@ -197,7 +197,7 @@ impl Display for ColumnDescriptor { pub struct PreparedQuery<'heap, A: Allocator> { pub vertex_type: VertexType, pub parameters: Parameters<'heap, A>, - pub projections: Projections, + pub(crate) projections: Projections, pub statement: SelectStatement, pub columns: Vec, } @@ -246,14 +246,6 @@ pub struct PostgresCompiler<'eval, 'ctx, 'heap, A: Allocator, S: Allocator> { alloc: A, scratch: S, - - /// Pre-built expression to subtract protected property keys from JSONB columns. - /// - /// When present, `properties` and `property_metadata` `SELECT` expressions are - /// wrapped as `(column - mask)`. The caller builds this from the permission - /// system's protection rules; the compiler doesn't know about entity types - /// or actors. - property_mask: Option, } impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> @@ -269,21 +261,9 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> context, alloc, scratch, - property_mask: None, } } - /// Sets an optional JSONB key mask applied to selected property columns. - /// - /// When set, `properties` and `property_metadata` selections are wrapped as - /// `(column - mask)` to strip protected keys from the output. The compiler itself does not - /// understand permissions; the caller is responsible for building the mask. - #[must_use] - pub fn with_property_mask(mut self, property_mask: Option) -> Self { - self.property_mask = property_mask; - self - } - /// Joins the property types across all filter bodies into a single type. /// /// Each filter body may operate on a different `Entity`. This computes the @@ -433,14 +413,7 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> for traversal_path in provides[VertexType::Entity].iter() { let TraversalPath::Entity(path) = traversal_path; - let mut expression = traverse::eval_entity_path(&mut db, path); - - if matches!(path, EntityPath::Properties | EntityPath::PropertyMetadata) - && let Some(mask) = &self.property_mask - { - expression = Expression::grouped(Expression::subtract(expression, mask.clone())); - } - + let expression = traverse::eval_entity_path(&mut db, path); let alias = Identifier::from(traversal_path.as_symbol().unwrap()); let field_type = traversal_path diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index 3e87a066169..56858dd6225 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -7,7 +7,7 @@ use core::alloc::Allocator; use hash_graph_postgres_store::store::postgres::query::{ self, Alias, Column, ColumnName, ColumnReference, ForeignKeyReference, FromItem, Identifier, JoinType, PostgresType, SelectExpression, SelectStatement, Table, TableName, TableReference, - table, + table::{self, DatabaseColumn as _}, }; use hashql_core::symbol::sym; @@ -215,23 +215,95 @@ impl Projections { from } + /// Builds `entity_editions` as a LATERAL subquery with explicit column projections. + /// + /// ```sql + /// INNER JOIN LATERAL ( + /// SELECT ee. AS , ... + /// FROM entity_editions AS ee + /// WHERE ee.edition_id = base.edition_id + /// ) AS ON TRUE + /// ``` + /// + /// The explicit projections let the authorization graft locate and replace + /// individual column expressions (e.g. applying a property mask to `properties`). pub(crate) fn build_entity_editions<'item>( &self, from: FromItem<'item>, alias: Alias, ) -> FromItem<'item> { - let fk = ForeignKeyReference::Single { - on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), - join: Column::EntityEditions(table::EntityEditions::EditionId), - join_type: JoinType::Inner, + let inner_ref = TableReference { + schema: None, + name: TableName::from("ee"), + alias: None, }; - from.join( - JoinType::Inner, - FromItem::table(Table::EntityEditions).alias(Table::EntityEditions.aliased(alias)), - ) - .on(fk.conditions(self.base_alias, alias)) - .build() + // entity_editions AS ee + let inner_from = FromItem::table(Table::EntityEditions) + .alias(inner_ref.clone()) + .build(); + + // ee.edition_id = base.edition_id + let correlation = query::Expression::equal( + query::Expression::ColumnReference(ColumnReference { + correlation: Some(inner_ref.clone()), + name: Column::EntityEditions(table::EntityEditions::EditionId).into(), + }), + query::Expression::ColumnReference(ColumnReference { + correlation: Some(self.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId) + .into(), + }), + ); + + // Project every column, `ee.[property] AS [property]`, this mirrors `*`, but makes each + // column available by name. + let selects = table::EntityEditions::ALL + .into_iter() + .map(|column| SelectExpression::Expression { + expression: query::Expression::ColumnReference(ColumnReference { + correlation: Some(inner_ref.clone()), + name: Column::EntityEditions(column).into(), + }), + alias: Some(column.as_str().into()), + }) + .collect(); + + // WHERE ee.edition_id = base.edition_id + let r#where = query::WhereExpression { + conditions: vec![correlation], + cursor: Vec::new(), + }; + + // SELECT + // ee.[property] AS [property], + // ... + // FROM entity_editions as ee + // WHERE ee.edition_id = base.edition_id + let subquery = SelectStatement::builder() + .selects(selects) + .from(inner_from) + .where_expression(r#where) + .build(); + + // LATERAL (subquery) AS [alias] + let lateral = FromItem::Subquery { + lateral: true, + statement: Box::new(subquery), + alias: Some(TableReference { + schema: None, + name: TableName::from(Table::EntityEditions), + alias: Some(alias), + }), + column_alias: vec![], + }; + + // INNER JOIN LATERAL (...) ON TRUE + from.join(JoinType::Inner, lateral) + .on(vec![query::Expression::Constant(query::Constant::Boolean( + true, + ))]) + .build() } pub(crate) fn build_entity_ids<'item>( From 48a8d2d288ca7edcbff29420f3120b9a6089cd37 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:41:50 +0200 Subject: [PATCH 10/34] feat: more policy into own module --- .../eval/src/postgres/authorization/mod.rs | 169 +++++++++++++++ .../policy.rs} | 199 ++---------------- libs/@local/hashql/eval/src/postgres/mod.rs | 69 ++---- .../hashql/eval/src/postgres/prepared.rs | 115 ++++++++++ 4 files changed, 320 insertions(+), 232 deletions(-) create mode 100644 libs/@local/hashql/eval/src/postgres/authorization/mod.rs rename libs/@local/hashql/eval/src/postgres/{authorization.rs => authorization/policy.rs} (70%) create mode 100644 libs/@local/hashql/eval/src/postgres/prepared.rs diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs new file mode 100644 index 00000000000..ca8fd6e8306 --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -0,0 +1,169 @@ +//! Converts authorization policies into SQL conditions for entity queries. +//! +//! The compilation pipeline produces actor-agnostic queries. This module grafts +//! actor-specific policy conditions onto those queries at runtime, just before +//! the orchestrator executes them. +//! +//! The entry point is [`analysis_in`], which takes a compiled [`PreparedQuery`] +//! and a [`PolicyComponents`] and produces an [`AnalysisResidual`] containing: +//! +//! - A combined WHERE condition (the permit/forbid algebra) +//! - Auxiliary parameter values referenced by that condition +//! - The [`AuthorizationProjections`] that tracks which joins need to be appended +use core::alloc::Allocator; + +use hash_graph_postgres_store::store::postgres::query::{ + Alias, Column, ForeignKeyReference, FromItem, JoinType, Table, TableName, TableReference, table, +}; +use postgres_types::ToSql; + +use super::projections::Projections; + +mod policy; + +/// Tracks joins that authorization conditions need. +/// +/// Accessors reuse joins from the base [`Projections`] when available, falling +/// back to fresh joins compiled by [`build_joins`](Self::build_joins). +struct AuthorizationProjections<'base> { + index: usize, + base: &'base Projections, + + entity_ids: Option, + entity_editions: Option, + entity_is_of_type_ids: Option, +} + +impl<'base> AuthorizationProjections<'base> { + const fn new(base: &'base Projections) -> Self { + Self { + index: base.index, + base, + entity_ids: None, + entity_editions: None, + entity_is_of_type_ids: None, + } + } + + const fn next_alias(&mut self) -> Alias { + let alias = Alias { + condition_index: 0, + chain_depth: 0, + number: self.index, + }; + self.index += 1; + alias + } + + fn temporal_metadata(&self) -> TableReference<'static> { + self.base.temporal_metadata() + } + + /// Entity-level provenance, joined on `(web_id, entity_uuid)`. + fn entity_ids(&mut self) -> TableReference<'static> { + let alias = if let Some(base_alias) = self.base.entity_ids { + base_alias + } else if let Some(alias) = self.entity_ids { + alias + } else { + let alias = self.next_alias(); + self.entity_ids = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityIds), + alias: Some(alias), + } + } + + /// Entity edition data, joined on `edition_id`. + fn entity_editions(&mut self) -> TableReference<'static> { + let alias = if let Some(base_alias) = self.base.entity_editions { + base_alias + } else if let Some(alias) = self.entity_editions { + alias + } else { + let alias = self.next_alias(); + self.entity_editions = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityEditions), + alias: Some(alias), + } + } + + /// Entity type assignments, joined on `entity_edition_id`. + /// + /// Always allocates a fresh join; the base projections' type aggregate + /// is a scoped LATERAL subquery and cannot be reused. + fn entity_is_of_type_ids(&mut self) -> TableReference<'static> { + let alias = if let Some(alias) = self.entity_is_of_type_ids { + alias + } else { + let alias = self.next_alias(); + self.entity_is_of_type_ids = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityIsOfTypeIds), + alias: Some(alias), + } + } + + /// Appends joins for tables not already present in the base projections. + fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { + if let Some(alias) = self.entity_ids { + from = self.base.build_entity_ids(from, alias); + } + + if let Some(alias) = self.entity_editions { + from = self.base.build_entity_editions(from, alias); + } + + if let Some(alias) = self.entity_is_of_type_ids { + let fk = ForeignKeyReference::Single { + on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), + join: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::EntityEditionId), + join_type: JoinType::Inner, + }; + + from = from + .join( + JoinType::Inner, + FromItem::table(Table::EntityIsOfTypeIds) + .alias(Table::EntityIsOfTypeIds.aliased(alias)), + ) + .on(fk.conditions(self.base.base_alias, alias)) + .build(); + } + + from + } +} + +/// Runtime parameter values for authorization conditions, indexed after +/// the compiled parameters (`$K+1..`). +struct AuxiliaryParameters { + initial_offset: usize, + parameters: Vec, A>, +} + +impl AuxiliaryParameters { + /// Pushes a value and returns its 1-based parameter index (`$N`). + fn push(&mut self, value: impl ToSql + Sync + 'static) -> usize + where + A: Clone, + { + let alloc = self.parameters.allocator().clone(); + self.parameters.push(Box::new_in(value, alloc)); + + self.parameters.len() + self.initial_offset + } +} diff --git a/libs/@local/hashql/eval/src/postgres/authorization.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs similarity index 70% rename from libs/@local/hashql/eval/src/postgres/authorization.rs rename to libs/@local/hashql/eval/src/postgres/authorization/policy.rs index a6e033e1f83..13b8b0020e9 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs @@ -1,16 +1,3 @@ -//! Converts authorization policies into SQL conditions for entity queries. -//! -//! The compilation pipeline produces actor-agnostic queries. This module grafts -//! actor-specific policy conditions onto those queries at runtime, just before -//! the orchestrator executes them. -//! -//! The entry point is [`analysis_in`], which takes a compiled [`PreparedQuery`] -//! and a [`PolicyComponents`] and produces an [`AnalysisResidual`] containing: -//! -//! - A combined WHERE condition (the permit/forbid algebra) -//! - Auxiliary parameter values referenced by that condition -//! - The [`AuthorizationProjections`] that tracks which joins need to be appended - use alloc::borrow::Cow; use core::alloc::Allocator; @@ -20,8 +7,8 @@ use hash_graph_authorization::policies::{ resource::{EntityResourceConstraint, EntityResourceFilter, ResourceConstraint}, }; use hash_graph_postgres_store::store::postgres::query::{ - Alias, BinaryExpression, BinaryOperator, Column, ColumnReference, Constant, Expression, - ForeignKeyReference, FromItem, JoinType, PostgresType, Table, TableName, TableReference, table, + BinaryExpression, BinaryOperator, Column, ColumnReference, Constant, Expression, PostgresType, + table, }; use hash_graph_store::filter::PathToken; use postgres_types::ToSql; @@ -30,170 +17,24 @@ use type_system::{ principal::actor::{ActorEntityUuid, ActorId}, }; -use super::{PreparedQuery, projections::Projections}; - -/// Tracks joins that authorization conditions need. -/// -/// Accessors reuse joins from the base [`Projections`] when available, falling -/// back to fresh joins compiled by [`build_joins`](Self::build_joins). -struct AuthorizationProjections<'base> { - index: usize, - base: &'base Projections, - - entity_ids: Option, - entity_editions: Option, - entity_is_of_type_ids: Option, -} - -impl<'base> AuthorizationProjections<'base> { - const fn new(base: &'base Projections) -> Self { - Self { - index: base.index, - base, - entity_ids: None, - entity_editions: None, - entity_is_of_type_ids: None, - } - } - - const fn next_alias(&mut self) -> Alias { - let alias = Alias { - condition_index: 0, - chain_depth: 0, - number: self.index, - }; - self.index += 1; - alias - } - - fn temporal_metadata(&self) -> TableReference<'static> { - self.base.temporal_metadata() - } - - /// Entity-level provenance, joined on `(web_id, entity_uuid)`. - fn entity_ids(&mut self) -> TableReference<'static> { - let alias = if let Some(base_alias) = self.base.entity_ids { - base_alias - } else if let Some(alias) = self.entity_ids { - alias - } else { - let alias = self.next_alias(); - self.entity_ids = Some(alias); - alias - }; - - TableReference { - schema: None, - name: TableName::from(Table::EntityIds), - alias: Some(alias), - } - } - - /// Entity edition data, joined on `edition_id`. - fn entity_editions(&mut self) -> TableReference<'static> { - let alias = if let Some(base_alias) = self.base.entity_editions { - base_alias - } else if let Some(alias) = self.entity_editions { - alias - } else { - let alias = self.next_alias(); - self.entity_editions = Some(alias); - alias - }; - - TableReference { - schema: None, - name: TableName::from(Table::EntityEditions), - alias: Some(alias), - } - } - - /// Entity type assignments, joined on `entity_edition_id`. - /// - /// Always allocates a fresh join; the base projections' type aggregate - /// is a scoped LATERAL subquery and cannot be reused. - fn entity_is_of_type_ids(&mut self) -> TableReference<'static> { - let alias = if let Some(alias) = self.entity_is_of_type_ids { - alias - } else { - let alias = self.next_alias(); - self.entity_is_of_type_ids = Some(alias); - alias - }; - - TableReference { - schema: None, - name: TableName::from(Table::EntityIsOfTypeIds), - alias: Some(alias), - } - } - - /// Appends joins for tables not already present in the base projections. - fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { - if let Some(alias) = self.entity_ids { - from = self.base.build_entity_ids(from, alias); - } - - if let Some(alias) = self.entity_editions { - from = self.base.build_entity_editions(from, alias); - } - - if let Some(alias) = self.entity_is_of_type_ids { - let fk = ForeignKeyReference::Single { - on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), - join: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::EntityEditionId), - join_type: JoinType::Inner, - }; - - from = from - .join( - JoinType::Inner, - FromItem::table(Table::EntityIsOfTypeIds) - .alias(Table::EntityIsOfTypeIds.aliased(alias)), - ) - .on(fk.conditions(self.base.base_alias, alias)) - .build(); - } - - from - } -} - -/// Runtime parameter values for authorization conditions, indexed after -/// the compiled parameters (`$K+1..`). -struct AuxiliaryParameters { - initial_offset: usize, - parameters: Vec, A>, -} - -impl AuxiliaryParameters { - /// Pushes a value and returns its 1-based parameter index (`$N`). - fn push(&mut self, value: impl ToSql + Sync + 'static) -> usize - where - A: Clone, - { - let alloc = self.parameters.allocator().clone(); - self.parameters.push(Box::new_in(value, alloc)); - - self.parameters.len() + self.initial_offset - } -} +use super::{AuthorizationProjections, AuxiliaryParameters}; +use crate::postgres::PreparedQuery; /// Context for policy-to-SQL conversion. -struct PreparedAnalysis<'query, A: Allocator> { +pub(crate) struct PreparedAnalysis<'query, A: Allocator> { projections: AuthorizationProjections<'query>, parameters: AuxiliaryParameters, actor_id: Option, } impl<'query, A: Allocator> PreparedAnalysis<'query, A> { - fn new_in( + pub(crate) fn new_in( query: &'query PreparedQuery<'_, impl Allocator>, policy: &PolicyComponents, alloc: A, ) -> Self { Self { - projections: AuthorizationProjections::new(&query.projections), + projections: AuthorizationProjections::new(query.projections()), parameters: AuxiliaryParameters { initial_offset: query.parameters.len(), parameters: Vec::new_in(alloc), @@ -203,14 +44,6 @@ impl<'query, A: Allocator> PreparedAnalysis<'query, A> { } } -/// Everything needed to graft authorization onto a compiled query. -struct AnalysisResidual<'query, A: Allocator> { - condition: Expression, - /// Parameter values referenced by `condition` via `$N` indices. - auxiliary_parameters: Vec, A>, - projections: AuthorizationProjections<'query>, -} - /// Checks whether the entity has a specific `(base_url, version)` type pair. /// /// ```sql @@ -471,15 +304,23 @@ fn optimize( } } +/// Everything needed to graft authorization onto a compiled query. +pub(super) struct PolicyResidual<'query, A: Allocator> { + pub condition: Expression, + /// Parameter values referenced by `condition` via `$N` indices. + pub auxiliary_parameters: Vec, A>, + pub projections: AuthorizationProjections<'query>, +} + /// Converts policies into a SQL condition using the permit/forbid algebra. /// /// `scratch` is used for temporary constraint collection. -fn analysis_in<'query, A: Allocator + Clone, S: Allocator>( +pub(crate) fn lower_policy<'query, A: Allocator + Clone, S: Allocator>( query: &'query PreparedQuery<'_, impl Allocator>, policy: &PolicyComponents, alloc: A, scratch: S, -) -> AnalysisResidual<'query, A> { +) -> PolicyResidual<'query, A> { let action = match query.vertex_type { hashql_mir::pass::execution::VertexType::Entity => ActionName::ViewEntity, }; @@ -499,10 +340,10 @@ fn analysis_in<'query, A: Allocator + Clone, S: Allocator>( } (Effect::Forbid, None) => { // Blank forbid: deny everything, no further analysis needed. - return AnalysisResidual { + return PolicyResidual { condition: Expression::Constant(Constant::Boolean(false)), auxiliary_parameters: Vec::new_in(alloc), - projections: AuthorizationProjections::new(&query.projections), + projections: AuthorizationProjections::new(query.projections()), }; } (Effect::Permit, Some(constraint)) => { @@ -553,7 +394,7 @@ fn analysis_in<'query, A: Allocator + Clone, S: Allocator>( (false, Some(permits), Some(forbids)) => Expression::all(vec![permits, forbids.not()]), }; - AnalysisResidual { + PolicyResidual { condition: expression, auxiliary_parameters: output.parameters.parameters, projections: output.projections, diff --git a/libs/@local/hashql/eval/src/postgres/mod.rs b/libs/@local/hashql/eval/src/postgres/mod.rs index 90beb53f7c1..18cb95b824b 100644 --- a/libs/@local/hashql/eval/src/postgres/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/mod.rs @@ -30,8 +30,8 @@ use core::{alloc::Allocator, fmt::Display}; use hash_graph_postgres_store::store::postgres::query::{ - self, Column, Expression, Identifier, SelectExpression, SelectStatement, Transpile as _, - WhereExpression, table::EntityTemporalMetadata, + self, Column, Expression, Identifier, SelectExpression, SelectStatement, WhereExpression, + table::EntityTemporalMetadata, }; use hashql_core::{ debug_panic, @@ -42,7 +42,6 @@ use hashql_core::{ use hashql_mir::{ body::{ Body, - basic_block::BasicBlockId, local::Local, terminator::{GraphRead, GraphReadBody, GraphReadHead, TerminatorKind}, }, @@ -63,6 +62,7 @@ use self::{ pub use self::{ continuation::ContinuationField, parameters::{Parameter, ParameterIndex, ParameterValue, Parameters, TemporalAxis}, + prepared::{PatchPreparedQuery, PreparedQueries, PreparedQuery, PreparedQueryPatch}, }; use crate::context::CodeGenerationContext; @@ -71,6 +71,7 @@ mod continuation; pub(crate) mod error; mod filter; mod parameters; +mod prepared; mod projections; mod traverse; mod types; @@ -189,51 +190,6 @@ impl Display for ColumnDescriptor { } } -/// A fully-compiled SQL query ready for execution. -/// -/// Contains the typed query AST ([`SelectStatement`]), the parameter catalog ([`Parameters`]) -/// for binding runtime values, and a column manifest ([`ColumnDescriptor`]s) that tells the -/// bridge how to decode each result column. -pub struct PreparedQuery<'heap, A: Allocator> { - pub vertex_type: VertexType, - pub parameters: Parameters<'heap, A>, - pub(crate) projections: Projections, - pub statement: SelectStatement, - pub columns: Vec, -} - -impl PreparedQuery<'_, A> { - pub fn transpile(&self) -> impl Display { - core::fmt::from_fn(|fmt| self.statement.transpile(fmt)) - } -} - -/// Registry of compiled SQL queries, indexed by definition and basic block. -/// -/// The SQL lowering pass produces one [`PreparedQuery`] per [`GraphRead`] -/// terminator in the MIR. This struct stores them contiguously in `queries` -/// with `offsets` providing per-definition starting positions, so -/// [`find`](Self::find) can locate the correct query for a given `(DefId, -/// BasicBlockId)` pair. -/// -/// [`GraphRead`]: hashql_mir::body::terminator::GraphRead -pub struct PreparedQueries<'heap, A: Allocator> { - offsets: Box, A>, - queries: Vec<(BasicBlockId, PreparedQuery<'heap, A>), A>, -} - -impl<'heap, A: Allocator> PreparedQueries<'heap, A> { - pub fn find(&self, body: DefId, block: BasicBlockId) -> Option<&PreparedQuery<'heap, A>> { - let start = self.offsets[body]; - let end = self.offsets[body.plus(1)]; - - self.queries[start..end] - .iter() - .find(|(id, _)| *id == block) - .map(|(_, query)| query) - } -} - /// Compiles Postgres-targeted MIR islands into a single PostgreSQL `SELECT`. /// /// Created per evaluation and used to compile [`GraphRead`] terminators. Compilation emits @@ -382,7 +338,10 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> } } - fn compile_graph_read_entity(&mut self, read: &GraphRead<'heap>) -> PreparedQuery<'heap, A> + fn compile_graph_read_entity( + &mut self, + read: &GraphRead<'heap>, + ) -> prepared::PreparedQuery<'heap, A> where A: Clone, { @@ -473,11 +432,12 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> .where_expression(db.where_expression) .build(); - PreparedQuery { + prepared::PreparedQuery { vertex_type: VertexType::Entity, parameters: db.parameters, statement: query, projections: db.projections, + auxiliary_parameters: Vec::new_in(self.alloc.clone()), columns, } } @@ -485,7 +445,10 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> /// Compiles a [`GraphRead`] into a [`PreparedQuery`]. /// /// [`GraphRead`]: hashql_mir::body::terminator::GraphRead - pub fn compile_graph_read(&mut self, read: &'ctx GraphRead<'heap>) -> PreparedQuery<'heap, A> + pub fn compile_graph_read( + &mut self, + read: &'ctx GraphRead<'heap>, + ) -> prepared::PreparedQuery<'heap, A> where A: Clone, { @@ -495,7 +458,7 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> } #[expect(unsafe_code)] - pub fn compile(&mut self) -> PreparedQueries<'heap, A> + pub fn compile(&mut self) -> prepared::PreparedQueries<'heap, A> where A: Clone, { @@ -526,6 +489,6 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> offsets[body_id.plus(1)] = queries.len(); } - PreparedQueries { offsets, queries } + prepared::PreparedQueries { offsets, queries } } } diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs new file mode 100644 index 00000000000..0082c8a008a --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -0,0 +1,115 @@ +#![expect( + clippy::field_scoped_visibility_modifiers, + reason = "internal module that is opaque to the outside" +)] +use core::{alloc::Allocator, fmt::Display}; + +use hash_graph_postgres_store::store::postgres::query::{SelectStatement, Transpile as _}; +use hashql_core::id::Id as _; +use hashql_mir::{ + body::basic_block::BasicBlockId, + def::{DefId, DefIdSlice}, + pass::execution::VertexType, +}; +use postgres_types::ToSql; + +use super::{ColumnDescriptor, Parameters, projections::Projections}; + +/// A fully-compiled SQL query ready for execution. +/// +/// Contains the typed query AST ([`SelectStatement`]), the parameter catalog ([`Parameters`]) +/// for binding runtime values, and a column manifest ([`ColumnDescriptor`]s) that tells the +/// bridge how to decode each result column. +pub struct PreparedQuery<'heap, A: Allocator> { + pub vertex_type: VertexType, + pub parameters: Parameters<'heap, A>, + pub statement: SelectStatement, + pub columns: Vec, + + pub(super) projections: Projections, + pub(super) auxiliary_parameters: Vec, A>, +} + +impl PreparedQuery<'_, A> { + pub fn transpile(&self) -> impl Display { + core::fmt::from_fn(|fmt| self.statement.transpile(fmt)) + } + + pub(crate) fn projections(&self) -> &Projections { + &self.projections + } +} + +pub trait PatchPreparedQuery { + fn patch_statement(&mut self, statement: &mut SelectStatement); +} + +impl PatchPreparedQuery for (T,) +where + T: PatchPreparedQuery, +{ + fn patch_statement(&mut self, statement: &mut SelectStatement) { + self.0.patch_statement(statement); + } +} + +impl PatchPreparedQuery for (T, U) +where + T: PatchPreparedQuery, + U: PatchPreparedQuery, +{ + fn patch_statement(&mut self, statement: &mut SelectStatement) { + self.0.patch_statement(statement); + self.1.patch_statement(statement); + } +} + +impl PatchPreparedQuery for F +where + F: FnMut(&mut SelectStatement), +{ + fn patch_statement(&mut self, statement: &mut SelectStatement) { + (self)(statement); + } +} + +pub struct PreparedQueryPatch { + patches: T, + parameters: Vec, A>, +} + +impl PreparedQueryPatch +where + T: PatchPreparedQuery, +{ + pub fn apply(mut self, query: &mut PreparedQuery) { + self.patches.patch_statement(&mut query.statement); + query.auxiliary_parameters.append(&mut self.parameters); + } +} + +/// Registry of compiled SQL queries, indexed by definition and basic block. +/// +/// The SQL lowering pass produces one [`PreparedQuery`] per [`GraphRead`] +/// terminator in the MIR. This struct stores them contiguously in `queries` +/// with `offsets` providing per-definition starting positions, so +/// [`find`](Self::find) can locate the correct query for a given `(DefId, +/// BasicBlockId)` pair. +/// +/// [`GraphRead`]: hashql_mir::body::terminator::GraphRead +pub struct PreparedQueries<'heap, A: Allocator> { + pub(crate) offsets: Box, A>, + pub(crate) queries: Vec<(BasicBlockId, PreparedQuery<'heap, A>), A>, +} + +impl<'heap, A: Allocator> PreparedQueries<'heap, A> { + pub fn find(&self, body: DefId, block: BasicBlockId) -> Option<&PreparedQuery<'heap, A>> { + let start = self.offsets[body]; + let end = self.offsets[body.plus(1)]; + + self.queries[start..end] + .iter() + .find(|(id, _)| *id == block) + .map(|(_, query)| query) + } +} From 7bcc002d85615d7a38151100c0733867bc87c38c Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:24:19 +0200 Subject: [PATCH 11/34] feat: protection translation unit --- libs/@local/graph/store/src/filter/mod.rs | 2 +- .../graph/store/src/filter/parameter.rs | 8 +- .../graph/store/src/filter/protection.rs | 68 ++++- .../eval/src/postgres/authorization/mod.rs | 30 +- .../eval/src/postgres/authorization/policy.rs | 279 ++++++++---------- .../src/postgres/authorization/protection.rs | 197 +++++++++++++ 6 files changed, 408 insertions(+), 176 deletions(-) create mode 100644 libs/@local/hashql/eval/src/postgres/authorization/protection.rs diff --git a/libs/@local/graph/store/src/filter/mod.rs b/libs/@local/graph/store/src/filter/mod.rs index 48123a2bf3b..81d098b655d 100644 --- a/libs/@local/graph/store/src/filter/mod.rs +++ b/libs/@local/graph/store/src/filter/mod.rs @@ -1419,7 +1419,7 @@ impl<'p> FilterExpression<'p, Entity> { actor_id: Option, ) -> Self { match expression { - PropertyFilterExpression::Path { path } => Self::Path { path }, + PropertyFilterExpression::Path { path } => Self::Path { path: path.into() }, PropertyFilterExpression::Parameter { parameter } => Self::Parameter { parameter, convert: None, diff --git a/libs/@local/graph/store/src/filter/parameter.rs b/libs/@local/graph/store/src/filter/parameter.rs index 51b10a5c015..81af215a64a 100644 --- a/libs/@local/graph/store/src/filter/parameter.rs +++ b/libs/@local/graph/store/src/filter/parameter.rs @@ -93,10 +93,12 @@ pub enum FilterExpressionList<'p, R: QueryRecord> { ParameterList { parameters: ParameterList<'p> }, } -impl<'p> From> for FilterExpressionList<'p, Entity> { - fn from(value: PropertyFilterExpressionList<'p>) -> Self { +impl From for FilterExpressionList<'_, Entity> { + fn from(value: PropertyFilterExpressionList) -> Self { match value { - PropertyFilterExpressionList::Path { path } => FilterExpressionList::Path { path }, + PropertyFilterExpressionList::Path { path } => { + FilterExpressionList::Path { path: path.into() } + } } } } diff --git a/libs/@local/graph/store/src/filter/protection.rs b/libs/@local/graph/store/src/filter/protection.rs index 9b18758e71a..c79b4e2900b 100644 --- a/libs/@local/graph/store/src/filter/protection.rs +++ b/libs/@local/graph/store/src/filter/protection.rs @@ -560,28 +560,69 @@ use crate::{ subgraph::edges::SharedEdgeKind, }; +/// Subset of [`EntityQueryPath`] that property protection filters can reference. +/// +/// Adding a new variant requires updating every consumer that lowers these +/// to SQL expressions (including the HashQL authorization graft). +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum PropertyFilterEntityQueryPath { + /// The entity's UUID (`entity_temporal_metadata.entity_uuid`). + Uuid, + /// The base URLs of the entity's type(s) (`entity_is_of_type_ids.base_urls`). + TypeBaseUrls, +} + +impl From for EntityQueryPath<'_> { + fn from(value: PropertyFilterEntityQueryPath) -> Self { + match value { + PropertyFilterEntityQueryPath::Uuid => EntityQueryPath::Uuid, + PropertyFilterEntityQueryPath::TypeBaseUrls => EntityQueryPath::EntityTypeEdge { + edge_kind: SharedEdgeKind::IsOfType, + path: EntityTypeQueryPath::BaseUrl, + inheritance_depth: None, + }, + } + } +} + +/// A single operand in a [`PropertyFilter`] comparison. #[derive(Debug, Clone, PartialEq)] pub enum PropertyFilterExpression<'p> { - Path { path: EntityQueryPath<'p> }, + /// A column reference resolved from an entity query path. + Path { path: PropertyFilterEntityQueryPath }, + /// A literal value bound as a query parameter. Parameter { parameter: Parameter<'p> }, + /// The UUID of the actor executing the query. + /// + /// Resolved to the public actor UUID when no actor is present. ActorId, } +/// An array-valued operand for [`PropertyFilter::In`]. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum PropertyFilterExpressionList<'p> { - Path { path: EntityQueryPath<'p> }, +pub enum PropertyFilterExpressionList { + /// An array column reference resolved from an entity query path. + Path { path: PropertyFilterEntityQueryPath }, } +/// Condition tree that controls when a property should be masked. +/// +/// Each protected property in [`PropertyProtectionFilterConfig`] is paired +/// with a `PropertyFilter` that evaluates to true when the property should +/// be stripped from the result. For example, the default configuration +/// masks email when the entity is a User and the actor is not the owner. #[derive(Debug, Clone, PartialEq)] pub enum PropertyFilter<'p> { + /// All conditions must hold. All(Vec), + /// At least one condition must hold. Any(Vec), + /// Scalar equality. Equal(PropertyFilterExpression<'p>, PropertyFilterExpression<'p>), + /// Scalar inequality. NotEqual(PropertyFilterExpression<'p>, PropertyFilterExpression<'p>), - In( - PropertyFilterExpression<'p>, - PropertyFilterExpressionList<'p>, - ), + /// Array containment (`lhs = ANY(rhs)`). + In(PropertyFilterExpression<'p>, PropertyFilterExpressionList), } #[derive(Debug, Default)] @@ -642,16 +683,12 @@ impl<'p> PropertyProtectionFilterConfig<'p> { )), }, PropertyFilterExpressionList::Path { - path: EntityQueryPath::EntityTypeEdge { - edge_kind: SharedEdgeKind::IsOfType, - path: EntityTypeQueryPath::BaseUrl, - inheritance_depth: None, - }, + path: PropertyFilterEntityQueryPath::TypeBaseUrls, }, ), PropertyFilter::NotEqual( PropertyFilterExpression::Path { - path: EntityQueryPath::Uuid, + path: PropertyFilterEntityQueryPath::Uuid, }, PropertyFilterExpression::ActorId, ), @@ -695,6 +732,11 @@ impl<'p> PropertyProtectionFilterConfig<'p> { } } + #[must_use] + pub const fn property_filters(&self) -> &HashMap> { + &self.property_filters + } + /// Returns the embedding exclusions map: entity type → properties to exclude. /// /// Use this to pre-filter entity properties before sending to embedding generation. diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index ca8fd6e8306..bb4458871fb 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -1,15 +1,11 @@ -//! Converts authorization policies into SQL conditions for entity queries. +//! Grafts actor-specific authorization onto compiled queries at runtime. //! -//! The compilation pipeline produces actor-agnostic queries. This module grafts -//! actor-specific policy conditions onto those queries at runtime, just before -//! the orchestrator executes them. +//! The compilation pipeline produces actor-agnostic queries. This module patches +//! them with two kinds of runtime conditions: //! -//! The entry point is [`analysis_in`], which takes a compiled [`PreparedQuery`] -//! and a [`PolicyComponents`] and produces an [`AnalysisResidual`] containing: -//! -//! - A combined WHERE condition (the permit/forbid algebra) -//! - Auxiliary parameter values referenced by that condition -//! - The [`AuthorizationProjections`] that tracks which joins need to be appended +//! - **Policy** ([`policy`]): permit/forbid admission conditions added to WHERE +//! - **Protection** ([`protection`]): property masking applied inside the entity_editions LATERAL +//! subquery use core::alloc::Allocator; use hash_graph_postgres_store::store::postgres::query::{ @@ -17,9 +13,10 @@ use hash_graph_postgres_store::store::postgres::query::{ }; use postgres_types::ToSql; -use super::projections::Projections; +use super::{PreparedQuery, projections::Projections}; mod policy; +mod protection; /// Tracks joins that authorization conditions need. /// @@ -55,6 +52,10 @@ impl<'base> AuthorizationProjections<'base> { alias } + const fn snapshot(&self) -> Self { + Self { ..*self } + } + fn temporal_metadata(&self) -> TableReference<'static> { self.base.temporal_metadata() } @@ -156,6 +157,13 @@ struct AuxiliaryParameters { } impl AuxiliaryParameters { + fn new(query: &PreparedQuery, alloc: A) -> Self { + Self { + initial_offset: query.parameters.len() + query.auxiliary_parameters.len(), + parameters: Vec::new_in(alloc), + } + } + /// Pushes a value and returns its 1-based parameter index (`$N`). fn push(&mut self, value: impl ToSql + Sync + 'static) -> usize where diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs index 13b8b0020e9..4aac81772de 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs @@ -11,7 +11,6 @@ use hash_graph_postgres_store::store::postgres::query::{ table, }; use hash_graph_store::filter::PathToken; -use postgres_types::ToSql; use type_system::{ ontology::{BaseUrl, VersionedUrl}, principal::actor::{ActorEntityUuid, ActorId}, @@ -20,30 +19,6 @@ use type_system::{ use super::{AuthorizationProjections, AuxiliaryParameters}; use crate::postgres::PreparedQuery; -/// Context for policy-to-SQL conversion. -pub(crate) struct PreparedAnalysis<'query, A: Allocator> { - projections: AuthorizationProjections<'query>, - parameters: AuxiliaryParameters, - actor_id: Option, -} - -impl<'query, A: Allocator> PreparedAnalysis<'query, A> { - pub(crate) fn new_in( - query: &'query PreparedQuery<'_, impl Allocator>, - policy: &PolicyComponents, - alloc: A, - ) -> Self { - Self { - projections: AuthorizationProjections::new(query.projections()), - parameters: AuxiliaryParameters { - initial_offset: query.parameters.len(), - parameters: Vec::new_in(alloc), - }, - actor_id: policy.actor_id(), - } - } -} - /// Checks whether the entity has a specific `(base_url, version)` type pair. /// /// ```sql @@ -54,13 +29,13 @@ impl<'query, A: Allocator> PreparedAnalysis<'query, A> { /// The overlap of position sets preserves the pairing invariant: a shared /// position means `base_urls[i] = $base AND versions[i] = $version`. fn convert_is_of_type( - output: &mut PreparedAnalysis<'_, A>, + unit: &mut PolicyTranslationUnit<'_, '_, A>, url: VersionedUrl, ) -> Expression { - let table = output.projections.entity_is_of_type_ids(); + let table = unit.projections.entity_is_of_type_ids(); - let base_url_index = output.parameters.push(url.base_url); - let version_index = output.parameters.push(url.version); + let base_url_index = unit.parameters.push(url.base_url); + let version_index = unit.parameters.push(url.version); // array_positions(eit.base_urls, $base) let base_url_positions = Expression::Function( @@ -98,17 +73,17 @@ fn convert_is_of_type( /// $base = ANY(eit.base_urls) /// ``` fn convert_is_of_base_type( - output: &mut PreparedAnalysis<'_, A>, + unit: &mut PolicyTranslationUnit<'_, '_, A>, base_url: BaseUrl, ) -> Expression { - let base_url_index = output.parameters.push(base_url); + let base_url_index = unit.parameters.push(base_url); // $base = ANY(eit.base_urls) Expression::Binary(BinaryExpression { op: BinaryOperator::In, left: Box::new(Expression::Parameter(base_url_index)), right: Box::new(Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.entity_is_of_type_ids()), + correlation: Some(unit.projections.entity_is_of_type_ids()), name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::BaseUrls).into(), })), }) @@ -119,16 +94,16 @@ fn convert_is_of_base_type( /// Uses entity-level provenance from `entity_ids` (the original creator). /// Anonymous requests compare against the public actor UUID. fn convert_created_by_principal( - output: &mut PreparedAnalysis<'_, A>, + unit: &mut PolicyTranslationUnit<'_, '_, A>, ) -> Expression { - let actor_uuid = output + let actor_uuid = unit .actor_id .map_or_else(ActorEntityUuid::public_actor, ActorEntityUuid::from); - let actor_index = output.parameters.push(actor_uuid); + let actor_index = unit.parameters.push(actor_uuid); // ids.provenance->>'createdById' let provenance = Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.entity_ids()), + correlation: Some(unit.projections.entity_ids()), name: Column::EntityIds(table::EntityIds::Provenance).into(), }); @@ -148,32 +123,30 @@ fn convert_created_by_principal( /// Converts an [`EntityResourceFilter`] tree into a SQL [`Expression`]. fn convert_entity_resource_filter( - output: &mut PreparedAnalysis<'_, A>, + unit: &mut PolicyTranslationUnit<'_, '_, A>, filter: &EntityResourceFilter, ) -> Expression { match filter { EntityResourceFilter::All { filters } => Expression::all( filters .iter() - .map(|filter| convert_entity_resource_filter(output, filter)) + .map(|filter| convert_entity_resource_filter(unit, filter)) .collect(), ), EntityResourceFilter::Any { filters } => Expression::any( filters .iter() - .map(|filter| convert_entity_resource_filter(output, filter)) + .map(|filter| convert_entity_resource_filter(unit, filter)) .collect(), ), - EntityResourceFilter::Not { filter } => { - convert_entity_resource_filter(output, filter).not() - } + EntityResourceFilter::Not { filter } => convert_entity_resource_filter(unit, filter).not(), EntityResourceFilter::IsOfType { entity_type } => { - convert_is_of_type(output, entity_type.clone()) + convert_is_of_type(unit, entity_type.clone()) } EntityResourceFilter::IsOfBaseType { entity_type } => { - convert_is_of_base_type(output, entity_type.clone()) + convert_is_of_base_type(unit, entity_type.clone()) } - EntityResourceFilter::CreatedByPrincipal => convert_created_by_principal(output), + EntityResourceFilter::CreatedByPrincipal => convert_created_by_principal(unit), } } @@ -182,17 +155,17 @@ fn convert_entity_resource_filter( /// Non-entity resource types (entity types, property types, data types, meta) /// produce `FALSE` since they cannot match entity rows. fn convert_resource_constraint( - output: &mut PreparedAnalysis<'_, A>, + unit: &mut PolicyTranslationUnit<'_, '_, A>, constraint: &ResourceConstraint, ) -> Expression { match constraint { &ResourceConstraint::Web { web_id } => { - let index = output.parameters.push(web_id); + let index = unit.parameters.push(web_id); // base.web_id = $N Expression::equal( Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), + correlation: Some(unit.projections.temporal_metadata()), name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId) .into(), }), @@ -200,12 +173,12 @@ fn convert_resource_constraint( ) } &ResourceConstraint::Entity(EntityResourceConstraint::Exact { id }) => { - let index = output.parameters.push(id); + let index = unit.parameters.push(id); // base.entity_uuid = $N Expression::equal( Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), + correlation: Some(unit.projections.temporal_metadata()), name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) .into(), }), @@ -214,13 +187,13 @@ fn convert_resource_constraint( } ResourceConstraint::Entity(EntityResourceConstraint::Web { web_id, filter }) => { let lhs = - convert_resource_constraint(output, &ResourceConstraint::Web { web_id: *web_id }); - let rhs = convert_entity_resource_filter(output, filter); + convert_resource_constraint(unit, &ResourceConstraint::Web { web_id: *web_id }); + let rhs = convert_entity_resource_filter(unit, filter); Expression::all(vec![lhs, rhs]) } ResourceConstraint::Entity(EntityResourceConstraint::Any { filter }) => { - convert_entity_resource_filter(output, filter) + convert_entity_resource_filter(unit, filter) } ResourceConstraint::EntityType(_) | ResourceConstraint::PropertyType(_) @@ -231,7 +204,7 @@ fn convert_resource_constraint( /// Adds batched permit expressions from pre-analyzed [`OptimizationData`]. fn optimize( - output: &mut PreparedAnalysis<'_, A>, + unit: &mut PolicyTranslationUnit<'_, '_, A>, permits: &mut Option>, OptimizationData { permitted_entity_uuids, @@ -244,12 +217,12 @@ fn optimize( match &**permitted_entity_uuids { [] => {} &[entity_uuid] => { - let index = output.parameters.push(entity_uuid); + let index = unit.parameters.push(entity_uuid); // base.entity_uuid = $N permits.get_or_insert_default().push(Expression::equal( Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), + correlation: Some(unit.projections.temporal_metadata()), name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) .into(), }), @@ -257,12 +230,12 @@ fn optimize( )); } entity_uuids => { - let index = output.parameters.push(entity_uuids.to_vec()); + let index = unit.parameters.push(entity_uuids.to_vec()); // base.entity_uuid = ANY($N::uuid[]) permits.get_or_insert_default().push(Expression::r#in( Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), + correlation: Some(unit.projections.temporal_metadata()), name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid) .into(), }), @@ -275,12 +248,12 @@ fn optimize( match &**permitted_web_ids { [] => {} &[web_id] => { - let index = output.parameters.push(web_id); + let index = unit.parameters.push(web_id); // base.web_id = $N permits.get_or_insert_default().push(Expression::equal( Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), + correlation: Some(unit.projections.temporal_metadata()), name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId) .into(), }), @@ -288,12 +261,12 @@ fn optimize( )); } web_ids => { - let index = output.parameters.push(web_ids.to_vec()); + let index = unit.parameters.push(web_ids.to_vec()); // base.web_id = ANY($N::uuid[]) permits.get_or_insert_default().push(Expression::r#in( Expression::ColumnReference(ColumnReference { - correlation: Some(output.projections.temporal_metadata()), + correlation: Some(unit.projections.temporal_metadata()), name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::WebId) .into(), }), @@ -304,99 +277,109 @@ fn optimize( } } -/// Everything needed to graft authorization onto a compiled query. -pub(super) struct PolicyResidual<'query, A: Allocator> { +/// The WHERE condition produced by lowering permit/forbid policies. +pub(super) struct PolicyTranslation { pub condition: Expression, - /// Parameter values referenced by `condition` via `$N` indices. - pub auxiliary_parameters: Vec, A>, - pub projections: AuthorizationProjections<'query>, } -/// Converts policies into a SQL condition using the permit/forbid algebra. +/// Lowers [`PolicyComponents`] into a SQL condition. /// -/// `scratch` is used for temporary constraint collection. -pub(crate) fn lower_policy<'query, A: Allocator + Clone, S: Allocator>( - query: &'query PreparedQuery<'_, impl Allocator>, - policy: &PolicyComponents, +/// Borrows shared projections and parameters so that multiple lowering +/// passes (policy, protection) accumulate into the same patch. +pub(crate) struct PolicyTranslationUnit<'parent, 'query, A: Allocator> { + projections: &'parent mut AuthorizationProjections<'query>, + parameters: &'parent mut AuxiliaryParameters, + actor_id: Option, alloc: A, - scratch: S, -) -> PolicyResidual<'query, A> { - let action = match query.vertex_type { - hashql_mir::pass::execution::VertexType::Entity => ActionName::ViewEntity, - }; - let policies = policy.extract_filter_policies(action); - let optimization_data = policy.optimization_data(action); - - let mut permit_constraints = Vec::new_in(&scratch); - let mut forbid_constraints = Vec::new_in(&scratch); - let mut blank_permit = false; - - for (effect, constraint) in policies { - match (effect, constraint) { - (Effect::Permit, _) if blank_permit => {} - (Effect::Permit, None) => { - blank_permit = true; - permit_constraints.clear(); - } - (Effect::Forbid, None) => { - // Blank forbid: deny everything, no further analysis needed. - return PolicyResidual { - condition: Expression::Constant(Constant::Boolean(false)), - auxiliary_parameters: Vec::new_in(alloc), - projections: AuthorizationProjections::new(query.projections()), - }; - } - (Effect::Permit, Some(constraint)) => { - permit_constraints.push(constraint); - } - (Effect::Forbid, Some(constraint)) => { - forbid_constraints.push(constraint); +} + +impl<'proj, 'query, A: Allocator> PolicyTranslationUnit<'proj, 'query, A> { + pub(crate) fn transpile( + &mut self, + query: &PreparedQuery<'_, A>, + policy: &PolicyComponents, + scratch: S, + ) -> PolicyTranslation + where + S: Allocator, + A: Clone, + { + let action = match query.vertex_type { + hashql_mir::pass::execution::VertexType::Entity => ActionName::ViewEntity, + }; + let projections_snapshot = self.projections.snapshot(); + + let policies = policy.extract_filter_policies(action); + let optimization_data = policy.optimization_data(action); + + let mut permit_constraints = Vec::new_in(&scratch); + let mut forbid_constraints = Vec::new_in(&scratch); + let mut blank_permit = false; + + for (effect, constraint) in policies { + match (effect, constraint) { + (Effect::Permit, _) if blank_permit => {} + (Effect::Permit, None) => { + blank_permit = true; + permit_constraints.clear(); + } + (Effect::Forbid, None) => { + // reset the projections to be from what they have been before + *self.projections = projections_snapshot; + + // Blank forbid: deny everything, no further analysis needed. + return PolicyTranslation { + condition: Expression::Constant(Constant::Boolean(false)), + }; + } + (Effect::Permit, Some(constraint)) => { + permit_constraints.push(constraint); + } + (Effect::Forbid, Some(constraint)) => { + forbid_constraints.push(constraint); + } } } - } - // Phase 2: lower only surviving constraints to SQL - let mut output = PreparedAnalysis::new_in(query, policy, alloc); - - let mut permits = Some(permit_constraints) - .filter(|constraints| !constraints.is_empty()) - .map(|constraints| { - constraints - .into_iter() - .map(|constraint| convert_resource_constraint(&mut output, constraint)) - .collect() - }); - if !blank_permit { - optimize(&mut output, &mut permits, optimization_data); - } - let permits = permits.map(Expression::any); - - let forbids = Some(forbid_constraints) - .filter(|constraints| !constraints.is_empty()) - .map(|constraints| { - constraints - .into_iter() - .map(|constraint| convert_resource_constraint(&mut output, constraint)) - .collect() - }) - .map(Expression::any); - - let expression = match (blank_permit, permits, forbids) { - // blank permit, no forbids: allow all - (true, _, None) => Expression::Constant(Constant::Boolean(true)), - // blank permit + forbids: allow everything except forbidden - (true, _, Some(forbids)) => forbids.not(), - // no permits at all: deny all - (false, None, _) => Expression::Constant(Constant::Boolean(false)), - // constrained permits only - (false, Some(permits), None) => permits, - // constrained permits + forbids - (false, Some(permits), Some(forbids)) => Expression::all(vec![permits, forbids.not()]), - }; - - PolicyResidual { - condition: expression, - auxiliary_parameters: output.parameters.parameters, - projections: output.projections, + // Phase 2: lower only surviving constraints to SQL + let mut permits = Some(permit_constraints) + .filter(|constraints| !constraints.is_empty()) + .map(|constraints| { + constraints + .into_iter() + .map(|constraint| convert_resource_constraint(self, constraint)) + .collect() + }); + if !blank_permit { + optimize(self, &mut permits, optimization_data); + } + let permits = permits.map(Expression::any); + + let forbids = Some(forbid_constraints) + .filter(|constraints| !constraints.is_empty()) + .map(|constraints| { + constraints + .into_iter() + .map(|constraint| convert_resource_constraint(self, constraint)) + .collect() + }) + .map(Expression::any); + + let expression = match (blank_permit, permits, forbids) { + // blank permit, no forbids: allow all + (true, _, None) => Expression::Constant(Constant::Boolean(true)), + // blank permit + forbids: allow everything except forbidden + (true, _, Some(forbids)) => forbids.not(), + // no permits at all: deny all + (false, None, _) => Expression::Constant(Constant::Boolean(false)), + // constrained permits only + (false, Some(permits), None) => permits, + // constrained permits + forbids + (false, Some(permits), Some(forbids)) => Expression::all(vec![permits, forbids.not()]), + }; + + PolicyTranslation { + condition: expression, + } } } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection.rs new file mode 100644 index 00000000000..eab9d9f5c2f --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection.rs @@ -0,0 +1,197 @@ +use core::alloc::Allocator; + +use hash_graph_authorization::policies::PolicyComponents; +use hash_graph_postgres_store::store::postgres::query::{ + Column, ColumnReference, Expression, Function, PostgresType, table, +}; +use hash_graph_store::filter::{ + Parameter, + protection::{ + PropertyFilter, PropertyFilterEntityQueryPath, PropertyFilterExpression, + PropertyFilterExpressionList, PropertyProtectionFilterConfig, + }, +}; +use type_system::{ + ontology::BaseUrl, + principal::actor::{ActorEntityUuid, ActorId}, +}; + +use super::{AuthorizationProjections, AuxiliaryParameters}; + +/// Resolves a query path to its backing column. +fn resolve_path( + unit: &mut ProtectionTranslationUnit<'_, '_, A>, + path: PropertyFilterEntityQueryPath, +) -> Expression { + match path { + // base.entity_uuid + PropertyFilterEntityQueryPath::Uuid => Expression::ColumnReference(ColumnReference { + correlation: Some(unit.projections.temporal_metadata()), + name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid).into(), + }), + // eit.base_urls + PropertyFilterEntityQueryPath::TypeBaseUrls => { + Expression::ColumnReference(ColumnReference { + correlation: Some(unit.projections.entity_is_of_type_ids()), + name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::BaseUrls).into(), + }) + } + } +} + +/// Resolves a filter operand to a SQL expression. +fn resolve_expression( + unit: &mut ProtectionTranslationUnit<'_, '_, A>, + expression: &PropertyFilterExpression<'_>, +) -> Expression { + match expression { + &PropertyFilterExpression::Path { path } => resolve_path(unit, path), + PropertyFilterExpression::Parameter { parameter } => { + let index = match parameter { + &Parameter::Boolean(bool) => unit.parameters.push(bool), + Parameter::Decimal(decimal) => unit.parameters.push(decimal.clone()), + Parameter::Text(text) => unit.parameters.push(text.clone().into_owned()), + Parameter::Vector(embedding) => unit.parameters.push(embedding.to_owned()), + Parameter::Any(value) => unit.parameters.push(value.clone()), + &Parameter::Uuid(uuid) => unit.parameters.push(uuid), + Parameter::OntologyTypeVersion(version) => { + unit.parameters.push(version.clone().into_owned()) + } + &Parameter::Timestamp(timestamp) => unit.parameters.push(timestamp), + }; + + Expression::Parameter(index) + } + PropertyFilterExpression::ActorId => { + let actor_uuid = unit + .actor_id + .map_or_else(ActorEntityUuid::public_actor, ActorEntityUuid::from); + let index = unit.parameters.push(actor_uuid); + + Expression::Parameter(index) + } + } +} + +/// Lowers a [`PropertyFilter`] condition tree into a SQL boolean expression. +fn lower_filter( + unit: &mut ProtectionTranslationUnit<'_, '_, A>, + filter: &PropertyFilter<'_>, +) -> Expression { + match filter { + PropertyFilter::All(filters) => Expression::all( + filters + .iter() + .map(|filter| lower_filter(unit, filter)) + .collect(), + ), + PropertyFilter::Any(filters) => Expression::any( + filters + .iter() + .map(|filter| lower_filter(unit, filter)) + .collect(), + ), + PropertyFilter::Equal(lhs, rhs) => { + let lhs = resolve_expression(unit, lhs); + let rhs = resolve_expression(unit, rhs); + + Expression::equal(lhs, rhs) + } + PropertyFilter::NotEqual(lhs, rhs) => { + let lhs = resolve_expression(unit, lhs); + let rhs = resolve_expression(unit, rhs); + + Expression::not_equal(lhs, rhs) + } + // $lhs = ANY($rhs) + PropertyFilter::In(lhs, rhs) => { + let lhs = resolve_expression(unit, lhs); + let rhs = match rhs { + &PropertyFilterExpressionList::Path { path } => resolve_path(unit, path), + }; + + Expression::r#in(lhs, rhs) + } + } +} + +/// Builds the mask expression for a single protected property. +/// +/// ```sql +/// CASE WHEN +/// THEN ARRAY[$url]::text[] +/// ELSE '{}'::text[] +/// END +/// ``` +/// +/// Evaluates to a single-element array containing the property's base URL +/// when the condition holds (property should be masked), or an empty array +/// otherwise. +fn lower_property_filter( + unit: &mut ProtectionTranslationUnit<'_, '_, A>, + property_url: BaseUrl, + filter: &PropertyFilter<'_>, +) -> Expression { + let condition = lower_filter(unit, filter); + let url_index = unit.parameters.push(property_url); + + // CASE WHEN THEN ARRAY[$url]::text[] ELSE '{}'::text[] END + Expression::CaseWhen { + conditions: vec![( + condition, + Expression::Function(Function::ArrayLiteral { + elements: vec![Expression::Parameter(url_index)], + element_type: PostgresType::Text, + }), + )], + else_result: Some(Box::new(Expression::Function(Function::ArrayLiteral { + elements: vec![], + element_type: PostgresType::Text, + }))), + } +} + +/// The mask expression produced by lowering property protection rules. +/// +/// When present, this expression evaluates to a `text[]` of property base URLs +/// that should be stripped. It is subtracted from the `properties` and +/// `property_metadata` columns inside the entity_editions LATERAL subquery. +pub(super) struct ProtectionTranslation { + /// `NULL` when no properties are protected (no masking needed). + pub keys_to_remove: Option, +} + +/// Lowers [`PropertyProtectionFilterConfig`] into a mask expression. +/// +/// Borrows shared projections and parameters so that multiple lowering +/// passes (policy, protection) accumulate into the same patch. +pub(crate) struct ProtectionTranslationUnit<'parent, 'query, A: Allocator> { + projections: &'parent mut AuthorizationProjections<'query>, + parameters: &'parent mut AuxiliaryParameters, + actor_id: Option, +} + +impl ProtectionTranslationUnit<'_, '_, A> { + pub(crate) fn transpile( + &mut self, + policy: &PolicyComponents, + config: &PropertyProtectionFilterConfig<'_>, + ) -> ProtectionTranslation { + if config.is_empty() || policy.is_instance_admin() { + return ProtectionTranslation { + keys_to_remove: None, + }; + } + + let cases: Vec<_> = config + .property_filters() + .iter() + .map(|(property_url, filter)| lower_property_filter(self, property_url.clone(), filter)) + .collect(); + + // array_cat(CASE ..., CASE ..., ...) + ProtectionTranslation { + keys_to_remove: Some(Expression::concatenate(cases)), + } + } +} From 059697ca6505da068f5fe20b78d04ceab548f763 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:55:36 +0200 Subject: [PATCH 12/34] feat: checkpoint --- .../eval/src/postgres/authorization/mod.rs | 204 ++++-------------- .../eval/src/postgres/authorization/policy.rs | 20 +- .../src/postgres/authorization/protection.rs | 13 +- libs/@local/hashql/eval/src/postgres/mod.rs | 9 +- .../hashql/eval/src/postgres/parameters.rs | 28 +++ .../hashql/eval/src/postgres/prepared.rs | 120 ++++++++--- .../hashql/eval/src/postgres/projections.rs | 143 +++++++++++- 7 files changed, 324 insertions(+), 213 deletions(-) diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index bb4458871fb..543e2503edc 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -8,170 +8,54 @@ //! subquery use core::alloc::Allocator; -use hash_graph_postgres_store::store::postgres::query::{ - Alias, Column, ForeignKeyReference, FromItem, JoinType, Table, TableName, TableReference, table, +use hash_graph_authorization::policies::PolicyComponents; +use hash_graph_postgres_store::store::postgres::query::SelectStatement; +use hash_graph_store::filter::protection::PropertyProtectionFilterConfig; +use hashql_mir::pass::execution::VertexType; + +use self::{ + policy::{PolicyTranslation, PolicyTranslationUnit}, + protection::{ProtectionTranslation, ProtectionTranslationUnit}, }; -use postgres_types::ToSql; - -use super::{PreparedQuery, projections::Projections}; +use super::prepared::PatchContext; mod policy; mod protection; -/// Tracks joins that authorization conditions need. -/// -/// Accessors reuse joins from the base [`Projections`] when available, falling -/// back to fresh joins compiled by [`build_joins`](Self::build_joins). -struct AuthorizationProjections<'base> { - index: usize, - base: &'base Projections, - - entity_ids: Option, - entity_editions: Option, - entity_is_of_type_ids: Option, +pub struct AuthorizationPatch<'policy, 'path> { + policy: &'policy PolicyComponents, + properties: &'policy PropertyProtectionFilterConfig<'path>, } -impl<'base> AuthorizationProjections<'base> { - const fn new(base: &'base Projections) -> Self { - Self { - index: base.index, - base, - entity_ids: None, - entity_editions: None, - entity_is_of_type_ids: None, - } - } - - const fn next_alias(&mut self) -> Alias { - let alias = Alias { - condition_index: 0, - chain_depth: 0, - number: self.index, - }; - self.index += 1; - alias - } - - const fn snapshot(&self) -> Self { - Self { ..*self } - } - - fn temporal_metadata(&self) -> TableReference<'static> { - self.base.temporal_metadata() - } - - /// Entity-level provenance, joined on `(web_id, entity_uuid)`. - fn entity_ids(&mut self) -> TableReference<'static> { - let alias = if let Some(base_alias) = self.base.entity_ids { - base_alias - } else if let Some(alias) = self.entity_ids { - alias - } else { - let alias = self.next_alias(); - self.entity_ids = Some(alias); - alias - }; - - TableReference { - schema: None, - name: TableName::from(Table::EntityIds), - alias: Some(alias), - } - } - - /// Entity edition data, joined on `edition_id`. - fn entity_editions(&mut self) -> TableReference<'static> { - let alias = if let Some(base_alias) = self.base.entity_editions { - base_alias - } else if let Some(alias) = self.entity_editions { - alias - } else { - let alias = self.next_alias(); - self.entity_editions = Some(alias); - alias - }; - - TableReference { - schema: None, - name: TableName::from(Table::EntityEditions), - alias: Some(alias), - } - } - - /// Entity type assignments, joined on `entity_edition_id`. - /// - /// Always allocates a fresh join; the base projections' type aggregate - /// is a scoped LATERAL subquery and cannot be reused. - fn entity_is_of_type_ids(&mut self) -> TableReference<'static> { - let alias = if let Some(alias) = self.entity_is_of_type_ids { - alias - } else { - let alias = self.next_alias(); - self.entity_is_of_type_ids = Some(alias); - alias - }; - - TableReference { - schema: None, - name: TableName::from(Table::EntityIsOfTypeIds), - alias: Some(alias), - } - } - - /// Appends joins for tables not already present in the base projections. - fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { - if let Some(alias) = self.entity_ids { - from = self.base.build_entity_ids(from, alias); - } - - if let Some(alias) = self.entity_editions { - from = self.base.build_entity_editions(from, alias); - } - - if let Some(alias) = self.entity_is_of_type_ids { - let fk = ForeignKeyReference::Single { - on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), - join: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::EntityEditionId), - join_type: JoinType::Inner, - }; - - from = from - .join( - JoinType::Inner, - FromItem::table(Table::EntityIsOfTypeIds) - .alias(Table::EntityIsOfTypeIds.aliased(alias)), - ) - .on(fk.conditions(self.base.base_alias, alias)) - .build(); - } - - from - } -} - -/// Runtime parameter values for authorization conditions, indexed after -/// the compiled parameters (`$K+1..`). -struct AuxiliaryParameters { - initial_offset: usize, - parameters: Vec, A>, -} - -impl AuxiliaryParameters { - fn new(query: &PreparedQuery, alloc: A) -> Self { - Self { - initial_offset: query.parameters.len() + query.auxiliary_parameters.len(), - parameters: Vec::new_in(alloc), - } - } - - /// Pushes a value and returns its 1-based parameter index (`$N`). - fn push(&mut self, value: impl ToSql + Sync + 'static) -> usize - where - A: Clone, - { - let alloc = self.parameters.allocator().clone(); - self.parameters.push(Box::new_in(value, alloc)); - - self.parameters.len() + self.initial_offset - } -} +// impl PatchPreparedQuery for AuthorizationPatch<'_, '_> +// { fn patch_statement( +// &mut self, +// context: &mut PatchContext<'_, '_, A>, +// statement: &mut SelectStatement, +// scratch: S, +// ) { +// let mut policy = PolicyTranslationUnit { +// projections: context.projections, +// parameters: context.parameters, +// actor_id: self.policy.actor_id(), +// alloc: context.alloc.clone(), +// }; +// let PolicyTranslation { condition } = +// policy.transpile(context.vertex_type, self.policy, &scratch); +// statement.where_expression.add_condition(condition); + +// // TODO: skip if entity_editions isn't requested +// let mut protection = ProtectionTranslationUnit { +// projections: context.projections, +// parameters: context.parameters, +// actor_id: self.policy.actor_id(), +// }; +// let ProtectionTranslation { keys_to_remove } = +// protection.transpile(self.policy, self.properties); + +// // we must now find the entity_editions column, and apply the filter if it exists, this +// is // done by simply scanning the from items for the name which we gave it + +// todo!() +// } +// } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs index 4aac81772de..5caf341aa1f 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs @@ -11,13 +11,15 @@ use hash_graph_postgres_store::store::postgres::query::{ table, }; use hash_graph_store::filter::PathToken; +use hashql_mir::pass::execution::VertexType; use type_system::{ ontology::{BaseUrl, VersionedUrl}, principal::actor::{ActorEntityUuid, ActorId}, }; -use super::{AuthorizationProjections, AuxiliaryParameters}; -use crate::postgres::PreparedQuery; +use crate::postgres::{ + PreparedQuery, parameters::AuxiliaryParameters, projections::AuxiliaryProjections, +}; /// Checks whether the entity has a specific `(base_url, version)` type pair. /// @@ -287,16 +289,16 @@ pub(super) struct PolicyTranslation { /// Borrows shared projections and parameters so that multiple lowering /// passes (policy, protection) accumulate into the same patch. pub(crate) struct PolicyTranslationUnit<'parent, 'query, A: Allocator> { - projections: &'parent mut AuthorizationProjections<'query>, - parameters: &'parent mut AuxiliaryParameters, - actor_id: Option, - alloc: A, + pub projections: &'parent mut AuxiliaryProjections<'query>, + pub parameters: &'parent mut AuxiliaryParameters, + pub actor_id: Option, + pub alloc: A, } -impl<'proj, 'query, A: Allocator> PolicyTranslationUnit<'proj, 'query, A> { +impl PolicyTranslationUnit<'_, '_, A> { pub(crate) fn transpile( &mut self, - query: &PreparedQuery<'_, A>, + vertex_type: VertexType, policy: &PolicyComponents, scratch: S, ) -> PolicyTranslation @@ -304,7 +306,7 @@ impl<'proj, 'query, A: Allocator> PolicyTranslationUnit<'proj, 'query, A> { S: Allocator, A: Clone, { - let action = match query.vertex_type { + let action = match vertex_type { hashql_mir::pass::execution::VertexType::Entity => ActionName::ViewEntity, }; let projections_snapshot = self.projections.snapshot(); diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection.rs index eab9d9f5c2f..c3a7c3b595c 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection.rs @@ -16,7 +16,7 @@ use type_system::{ principal::actor::{ActorEntityUuid, ActorId}, }; -use super::{AuthorizationProjections, AuxiliaryParameters}; +use crate::postgres::{parameters::AuxiliaryParameters, projections::AuxiliaryProjections}; /// Resolves a query path to its backing column. fn resolve_path( @@ -166,9 +166,9 @@ pub(super) struct ProtectionTranslation { /// Borrows shared projections and parameters so that multiple lowering /// passes (policy, protection) accumulate into the same patch. pub(crate) struct ProtectionTranslationUnit<'parent, 'query, A: Allocator> { - projections: &'parent mut AuthorizationProjections<'query>, - parameters: &'parent mut AuxiliaryParameters, - actor_id: Option, + pub projections: &'parent mut AuxiliaryProjections<'query>, + pub parameters: &'parent mut AuxiliaryParameters, + pub actor_id: Option, } impl ProtectionTranslationUnit<'_, '_, A> { @@ -177,7 +177,10 @@ impl ProtectionTranslationUnit<'_, '_, A> { policy: &PolicyComponents, config: &PropertyProtectionFilterConfig<'_>, ) -> ProtectionTranslation { - if config.is_empty() || policy.is_instance_admin() { + if config.is_empty() + || policy.is_instance_admin() + || self.projections.entity_edition_reference().is_none() + { return ProtectionTranslation { keys_to_remove: None, }; diff --git a/libs/@local/hashql/eval/src/postgres/mod.rs b/libs/@local/hashql/eval/src/postgres/mod.rs index 18cb95b824b..85728975424 100644 --- a/libs/@local/hashql/eval/src/postgres/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/mod.rs @@ -56,13 +56,13 @@ use hashql_mir::{ }; use self::{ - continuation::ContinuationColumn, filter::GraphReadFilterCompiler, projections::Projections, - types::traverse_struct, + continuation::ContinuationColumn, filter::GraphReadFilterCompiler, + parameters::AuxiliaryParameters, projections::Projections, types::traverse_struct, }; pub use self::{ continuation::ContinuationField, parameters::{Parameter, ParameterIndex, ParameterValue, Parameters, TemporalAxis}, - prepared::{PatchPreparedQuery, PreparedQueries, PreparedQuery, PreparedQueryPatch}, + prepared::{PreparedQueries, PreparedQuery, PreparedQueryPatch}, }; use crate::context::CodeGenerationContext; @@ -432,12 +432,13 @@ impl<'eval, 'ctx, 'heap, A: Allocator, S: BumpAllocator> .where_expression(db.where_expression) .build(); + let auxiliary_parameters = AuxiliaryParameters::new(&db.parameters, self.alloc.clone()); prepared::PreparedQuery { vertex_type: VertexType::Entity, parameters: db.parameters, statement: query, projections: db.projections, - auxiliary_parameters: Vec::new_in(self.alloc.clone()), + auxiliary_parameters, columns, } } diff --git a/libs/@local/hashql/eval/src/postgres/parameters.rs b/libs/@local/hashql/eval/src/postgres/parameters.rs index e929f18fb9e..6e749b0fdcd 100644 --- a/libs/@local/hashql/eval/src/postgres/parameters.rs +++ b/libs/@local/hashql/eval/src/postgres/parameters.rs @@ -22,6 +22,7 @@ use hashql_mir::{ body::{local::Local, place::FieldIndex}, interpret::value::Int, }; +use postgres_types::ToSql; id::newtype!( /// Index of a SQL parameter in the compiled query, rendered as `$N` by the SQL formatter. @@ -249,6 +250,33 @@ impl fmt::Display for Parameters<'_, A> { } } +/// Runtime parameter values for authorization conditions, indexed after +/// the compiled parameters (`$K+1..`). +pub struct AuxiliaryParameters { + initial_offset: usize, + parameters: Vec, A>, +} + +impl AuxiliaryParameters { + pub(crate) fn new(params: &Parameters<'_, A>, alloc: A) -> Self { + Self { + initial_offset: params.len(), + parameters: Vec::new_in(alloc), + } + } + + /// Pushes a value and returns its 1-based parameter index (`$N`). + pub(crate) fn push(&mut self, value: impl ToSql + Sync + 'static) -> usize + where + A: Clone, + { + let alloc = self.parameters.allocator().clone(); + self.parameters.push(Box::new_in(value, alloc)); + + self.parameters.len() + self.initial_offset + } +} + #[cfg(test)] mod tests { #![expect(clippy::min_ident_chars)] diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs index 0082c8a008a..efeae3e1545 100644 --- a/libs/@local/hashql/eval/src/postgres/prepared.rs +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -5,7 +5,7 @@ use core::{alloc::Allocator, fmt::Display}; use hash_graph_postgres_store::store::postgres::query::{SelectStatement, Transpile as _}; -use hashql_core::id::Id as _; +use hashql_core::{heap::BumpAllocator, id::Id as _}; use hashql_mir::{ body::basic_block::BasicBlockId, def::{DefId, DefIdSlice}, @@ -13,7 +13,11 @@ use hashql_mir::{ }; use postgres_types::ToSql; -use super::{ColumnDescriptor, Parameters, projections::Projections}; +use super::{ + ColumnDescriptor, Parameters, + parameters::AuxiliaryParameters, + projections::{AuxiliaryProjections, Projections}, +}; /// A fully-compiled SQL query ready for execution. /// @@ -27,7 +31,7 @@ pub struct PreparedQuery<'heap, A: Allocator> { pub columns: Vec, pub(super) projections: Projections, - pub(super) auxiliary_parameters: Vec, A>, + pub(super) auxiliary_parameters: AuxiliaryParameters, } impl PreparedQuery<'_, A> { @@ -35,56 +39,104 @@ impl PreparedQuery<'_, A> { core::fmt::from_fn(|fmt| self.statement.transpile(fmt)) } - pub(crate) fn projections(&self) -> &Projections { + pub(crate) const fn projections(&self) -> &Projections { &self.projections } } -pub trait PatchPreparedQuery { - fn patch_statement(&mut self, statement: &mut SelectStatement); +struct HCons { + head: H, + tail: T, } -impl PatchPreparedQuery for (T,) -where - T: PatchPreparedQuery, -{ - fn patch_statement(&mut self, statement: &mut SelectStatement) { - self.0.patch_statement(statement); - } +struct HNil; + +pub struct PatchContext<'base, A: Allocator> { + pub projections: AuxiliaryProjections<'base>, + pub alloc: A, } -impl PatchPreparedQuery for (T, U) -where - T: PatchPreparedQuery, - U: PatchPreparedQuery, -{ - fn patch_statement(&mut self, statement: &mut SelectStatement) { - self.0.patch_statement(statement); - self.1.patch_statement(statement); +trait PatchPreparedQuery { + fn patch_query( + &mut self, + context: &mut PatchContext<'_, A>, + query: &mut PreparedQuery<'_, A>, + scratch: S, + cont: impl FnOnce(&mut PatchContext<'_, A>, &mut PreparedQuery<'_, A>, S), + ); +} + +impl PatchPreparedQuery for HNil { + fn patch_query( + &mut self, + context: &mut PatchContext<'_, A>, + query: &mut PreparedQuery<'_, A>, + scratch: S, + _: impl FnOnce(&mut PatchContext<'_, A>, &mut PreparedQuery<'_, A>, S), + ) { + let from = query + .statement + .from + .take() + .unwrap_or_else(|| unreachable!("every prepared query must have a FROM clause")); + + query.statement.from = Some(context.projections.build_joins(from)); } } -impl PatchPreparedQuery for F +impl PatchPreparedQuery for HCons where - F: FnMut(&mut SelectStatement), + H: PatchPreparedQuery, + T: PatchPreparedQuery, { - fn patch_statement(&mut self, statement: &mut SelectStatement) { - (self)(statement); + fn patch_query( + &mut self, + context: &mut PatchContext<'_, A>, + query: &mut PreparedQuery<'_, A>, + scratch: S, + cont: impl FnOnce(&mut PatchContext<'_, A>, &mut PreparedQuery<'_, A>, S), + ) { + self.head + .patch_query(context, query, scratch, |context, query, scratch| { + self.tail.patch_query(context, query, scratch, cont) + }); } } -pub struct PreparedQueryPatch { +pub struct PreparedQueryPatch { patches: T, - parameters: Vec, A>, } -impl PreparedQueryPatch -where - T: PatchPreparedQuery, -{ - pub fn apply(mut self, query: &mut PreparedQuery) { - self.patches.patch_statement(&mut query.statement); - query.auxiliary_parameters.append(&mut self.parameters); +impl PreparedQueryPatch { + fn new() -> Self { + Self { patches: HNil } + } +} + +impl PreparedQueryPatch { + pub fn layer(self, other: T2) -> PreparedQueryPatch> { + PreparedQueryPatch { + patches: HCons { + head: other, + tail: self.patches, + }, + } + } + + pub fn apply( + &mut self, + query: &mut PreparedQuery, + scratch: S, + ) where + T: PatchPreparedQuery, + { + let alloc = query.columns.allocator().clone(); + + let mut projections = AuxiliaryProjections::new(&query.projections); + let mut context = PatchContext { projections, alloc }; + + self.patches + .patch_query(&mut context, query, scratch, |_, _, _| {}); } } diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index 56858dd6225..ca76a26c545 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -34,7 +34,7 @@ impl From for ColumnName<'_> { /// reference to it. The actual `FROM` tree is built once at the end via [`Self::build_from`]. #[derive(Debug, Clone)] pub(crate) struct Projections { - pub(crate) index: usize, + index: usize, /// Always present as the base table; everything joins through it. pub base_alias: Alias, @@ -502,3 +502,144 @@ impl Projections { .build() } } + +/// Tracks joins that authorization conditions need. +/// +/// Accessors reuse joins from the base [`Projections`] when available, falling +/// back to fresh joins compiled by [`build_joins`](Self::build_joins). +pub struct AuxiliaryProjections<'base> { + index: usize, + base: &'base Projections, + + pub entity_ids: Option, + pub entity_editions: Option, + pub entity_is_of_type_ids: Option, +} + +impl<'base> AuxiliaryProjections<'base> { + pub(crate) const fn new(base: &'base Projections) -> Self { + Self { + index: base.index, + base, + entity_ids: None, + entity_editions: None, + entity_is_of_type_ids: None, + } + } + + const fn next_alias(&mut self) -> Alias { + let alias = Alias { + condition_index: 0, + chain_depth: 0, + number: self.index, + }; + self.index += 1; + alias + } + + pub(crate) const fn snapshot(&self) -> Self { + Self { ..*self } + } + + pub(crate) fn temporal_metadata(&self) -> TableReference<'static> { + self.base.temporal_metadata() + } + + /// Entity-level provenance, joined on `(web_id, entity_uuid)`. + pub(crate) fn entity_ids(&mut self) -> TableReference<'static> { + let alias = if let Some(base_alias) = self.base.entity_ids { + base_alias + } else if let Some(alias) = self.entity_ids { + alias + } else { + let alias = self.next_alias(); + self.entity_ids = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityIds), + alias: Some(alias), + } + } + + /// Entity edition data, joined on `edition_id`. + pub(crate) fn entity_editions(&mut self) -> TableReference<'static> { + let alias = if let Some(base_alias) = self.base.entity_editions { + base_alias + } else if let Some(alias) = self.entity_editions { + alias + } else { + let alias = self.next_alias(); + self.entity_editions = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityEditions), + alias: Some(alias), + } + } + + pub(crate) fn entity_edition_reference(&self) -> Option> { + let alias = self.base.entity_editions.or(self.entity_editions)?; + + Some(TableReference { + schema: None, + name: TableName::from(Table::EntityEditions), + alias: Some(alias), + }) + } + + /// Entity type assignments, joined on `entity_edition_id`. + /// + /// Always allocates a fresh join; the base projections' type aggregate + /// is a scoped LATERAL subquery and cannot be reused. + pub(crate) fn entity_is_of_type_ids(&mut self) -> TableReference<'static> { + let alias = if let Some(alias) = self.entity_is_of_type_ids { + alias + } else { + let alias = self.next_alias(); + self.entity_is_of_type_ids = Some(alias); + alias + }; + + TableReference { + schema: None, + name: TableName::from(Table::EntityIsOfTypeIds), + alias: Some(alias), + } + } + + /// Appends joins for tables not already present in the base projections. + pub(crate) fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { + if let Some(alias) = self.entity_ids { + from = self.base.build_entity_ids(from, alias); + } + + if let Some(alias) = self.entity_editions { + from = self.base.build_entity_editions(from, alias); + } + + if let Some(alias) = self.entity_is_of_type_ids { + let fk = ForeignKeyReference::Single { + on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), + join: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::EntityEditionId), + join_type: JoinType::Inner, + }; + + from = from + .join( + JoinType::Inner, + FromItem::table(Table::EntityIsOfTypeIds) + .alias(Table::EntityIsOfTypeIds.aliased(alias)), + ) + .on(fk.conditions(self.base.base_alias, alias)) + .build(); + } + + from + } +} From 32cb4799acdd99356a05c2b0dc039397ceb934bf Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:08:55 +0200 Subject: [PATCH 13/34] feat: authorization code --- libs/@local/hashql/eval/src/lib.rs | 3 +- .../eval/src/postgres/authorization/mod.rs | 188 ++++++++++++++---- .../eval/src/postgres/authorization/policy.rs | 23 +-- .../src/postgres/authorization/protection.rs | 21 +- libs/@local/hashql/eval/src/postgres/mod.rs | 14 +- .../hashql/eval/src/postgres/parameters.rs | 2 +- .../hashql/eval/src/postgres/prepared.rs | 63 +++--- .../hashql/eval/src/postgres/projections.rs | 54 ++--- 8 files changed, 234 insertions(+), 134 deletions(-) diff --git a/libs/@local/hashql/eval/src/lib.rs b/libs/@local/hashql/eval/src/lib.rs index ef0fa2c5390..ec19faed689 100644 --- a/libs/@local/hashql/eval/src/lib.rs +++ b/libs/@local/hashql/eval/src/lib.rs @@ -14,8 +14,7 @@ iter_array_chunks, maybe_uninit_fill, impl_trait_in_assoc_type, - try_blocks, - variant_count + try_blocks )] #![cfg_attr(test, feature( // Library Features diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index 543e2503edc..ec64255d9a1 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -4,58 +4,172 @@ //! them with two kinds of runtime conditions: //! //! - **Policy** ([`policy`]): permit/forbid admission conditions added to WHERE -//! - **Protection** ([`protection`]): property masking applied inside the entity_editions LATERAL +//! - **Protection** ([`protection`]): property masking applied inside the `entity_editions` LATERAL //! subquery -use core::alloc::Allocator; +use core::{alloc::Allocator, mem}; use hash_graph_authorization::policies::PolicyComponents; -use hash_graph_postgres_store::store::postgres::query::SelectStatement; +use hash_graph_postgres_store::store::postgres::query::{ + Alias, Expression, FromItem, SelectExpression, TableReference, + table::{self, DatabaseColumn as _}, +}; use hash_graph_store::filter::protection::PropertyProtectionFilterConfig; -use hashql_mir::pass::execution::VertexType; use self::{ policy::{PolicyTranslation, PolicyTranslationUnit}, protection::{ProtectionTranslation, ProtectionTranslationUnit}, }; -use super::prepared::PatchContext; +use super::{PatchPreparedQueryLayer, prepared::PatchContext}; mod policy; mod protection; +fn find_from_by_alias<'from, 'id>( + from: &'from mut FromItem<'id>, + needle: Alias, +) -> Option<&'from mut FromItem<'id>> { + match from { + FromItem::Table { + only: _, + table: _, + alias: Some(TableReference { + alias: Some(alias), .. + }), + column_alias: _, + tablesample: _, + } if needle == *alias => Some(from), + FromItem::Subquery { + lateral: _, + statement: _, + alias: Some(TableReference { + alias: Some(alias), .. + }), + column_alias: _, + } if needle == *alias => Some(from), + FromItem::Function { + lateral: _, + function: _, + with_ordinality: _, + alias: Some(TableReference { + alias: Some(alias), .. + }), + column_alias: _, + } if needle == *alias => Some(from), + FromItem::JoinOn { + left, + join_type: _, + right, + condition: _, + } => find_from_by_alias(left, needle).or_else(|| find_from_by_alias(right, needle)), + FromItem::JoinUsing { + left: _, + join_type: _, + right: _, + columns: _, + alias: Some(TableReference { + alias: Some(alias), .. + }), + } if needle == *alias => Some(from), + FromItem::JoinUsing { + left, + join_type: _, + right, + columns: _, + alias: _, + } => find_from_by_alias(left, needle).or_else(|| find_from_by_alias(right, needle)), + FromItem::CrossJoin { left, right } => { + find_from_by_alias(left, needle).or_else(|| find_from_by_alias(right, needle)) + } + FromItem::NaturalJoin { + left, + join_type: _, + right, + } => find_from_by_alias(left, needle).or_else(|| find_from_by_alias(right, needle)), + FromItem::Table { .. } | FromItem::Subquery { .. } | FromItem::Function { .. } => None, + } +} + pub struct AuthorizationPatch<'policy, 'path> { policy: &'policy PolicyComponents, properties: &'policy PropertyProtectionFilterConfig<'path>, } -// impl PatchPreparedQuery for AuthorizationPatch<'_, '_> -// { fn patch_statement( -// &mut self, -// context: &mut PatchContext<'_, '_, A>, -// statement: &mut SelectStatement, -// scratch: S, -// ) { -// let mut policy = PolicyTranslationUnit { -// projections: context.projections, -// parameters: context.parameters, -// actor_id: self.policy.actor_id(), -// alloc: context.alloc.clone(), -// }; -// let PolicyTranslation { condition } = -// policy.transpile(context.vertex_type, self.policy, &scratch); -// statement.where_expression.add_condition(condition); - -// // TODO: skip if entity_editions isn't requested -// let mut protection = ProtectionTranslationUnit { -// projections: context.projections, -// parameters: context.parameters, -// actor_id: self.policy.actor_id(), -// }; -// let ProtectionTranslation { keys_to_remove } = -// protection.transpile(self.policy, self.properties); - -// // we must now find the entity_editions column, and apply the filter if it exists, this -// is // done by simply scanning the from items for the name which we gave it - -// todo!() -// } -// } +impl PatchPreparedQueryLayer + for AuthorizationPatch<'_, '_> +{ + fn patch_query( + &mut self, + context: &mut PatchContext<'_, A>, + query: &mut super::PreparedQuery<'_, A>, + scratch: S, + next: &mut N, + ) where + N: super::prepared::PatchPreparedQuery, + { + let mut policy = PolicyTranslationUnit { + projections: &mut context.projections, + parameters: &mut query.auxiliary_parameters, + actor_id: self.policy.actor_id(), + }; + let PolicyTranslation { condition } = + policy.transpile(query.vertex_type, self.policy, &scratch); + query.statement.where_expression.add_condition(condition); + + let mut protection = ProtectionTranslationUnit { + projections: &mut context.projections, + parameters: &mut query.auxiliary_parameters, + actor_id: self.policy.actor_id(), + }; + + let ProtectionTranslation { keys_to_remove } = + protection.transpile(self.policy, self.properties); + + next.patch_query(context, query, scratch); + + let Some(keys_to_remove) = keys_to_remove else { + return; + }; + + let Some(entity_edition_alias) = context.projections.entity_edition_alias() else { + // we remove the keys _last_, and only iff the entity_editions table is still requested + return; + }; + + let from = query + .statement + .from + .as_mut() + .unwrap_or_else(|| unreachable!("prepared queries always have a from value")); + + let Some(FromItem::Subquery { + lateral: _, + statement, + alias: _, + column_alias: _, + }) = find_from_by_alias(from, entity_edition_alias) + else { + unreachable!( + "entity_edition_alias not found in from clause, even though it has been requested" + ); + }; + + for column in &mut statement.selects { + let SelectExpression::Expression { + expression, + alias: Some(alias), + } = column + else { + unreachable!( + "selects must be expressions, see: projections::build_entity_editions" + ); + }; + + if alias.as_ref() == table::EntityEditions::Properties.as_str() + || alias.as_ref() == table::EntityEditions::PropertyMetadata.as_str() + { + let base = mem::replace(expression, Expression::Parameter(0)); + *expression = Expression::subtract(base, keys_to_remove.clone()); + } + } + } +} diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs index 5caf341aa1f..ddd1e2fd01d 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs @@ -17,9 +17,7 @@ use type_system::{ principal::actor::{ActorEntityUuid, ActorId}, }; -use crate::postgres::{ - PreparedQuery, parameters::AuxiliaryParameters, projections::AuxiliaryProjections, -}; +use crate::postgres::{parameters::AuxiliaryParameters, projections::AuxiliaryProjections}; /// Checks whether the entity has a specific `(base_url, version)` type pair. /// @@ -31,7 +29,7 @@ use crate::postgres::{ /// The overlap of position sets preserves the pairing invariant: a shared /// position means `base_urls[i] = $base AND versions[i] = $version`. fn convert_is_of_type( - unit: &mut PolicyTranslationUnit<'_, '_, A>, + unit: &mut PolicyTranslationUnit<'_, A>, url: VersionedUrl, ) -> Expression { let table = unit.projections.entity_is_of_type_ids(); @@ -75,7 +73,7 @@ fn convert_is_of_type( /// $base = ANY(eit.base_urls) /// ``` fn convert_is_of_base_type( - unit: &mut PolicyTranslationUnit<'_, '_, A>, + unit: &mut PolicyTranslationUnit<'_, A>, base_url: BaseUrl, ) -> Expression { let base_url_index = unit.parameters.push(base_url); @@ -96,7 +94,7 @@ fn convert_is_of_base_type( /// Uses entity-level provenance from `entity_ids` (the original creator). /// Anonymous requests compare against the public actor UUID. fn convert_created_by_principal( - unit: &mut PolicyTranslationUnit<'_, '_, A>, + unit: &mut PolicyTranslationUnit<'_, A>, ) -> Expression { let actor_uuid = unit .actor_id @@ -125,7 +123,7 @@ fn convert_created_by_principal( /// Converts an [`EntityResourceFilter`] tree into a SQL [`Expression`]. fn convert_entity_resource_filter( - unit: &mut PolicyTranslationUnit<'_, '_, A>, + unit: &mut PolicyTranslationUnit<'_, A>, filter: &EntityResourceFilter, ) -> Expression { match filter { @@ -157,7 +155,7 @@ fn convert_entity_resource_filter( /// Non-entity resource types (entity types, property types, data types, meta) /// produce `FALSE` since they cannot match entity rows. fn convert_resource_constraint( - unit: &mut PolicyTranslationUnit<'_, '_, A>, + unit: &mut PolicyTranslationUnit<'_, A>, constraint: &ResourceConstraint, ) -> Expression { match constraint { @@ -206,7 +204,7 @@ fn convert_resource_constraint( /// Adds batched permit expressions from pre-analyzed [`OptimizationData`]. fn optimize( - unit: &mut PolicyTranslationUnit<'_, '_, A>, + unit: &mut PolicyTranslationUnit<'_, A>, permits: &mut Option>, OptimizationData { permitted_entity_uuids, @@ -288,14 +286,13 @@ pub(super) struct PolicyTranslation { /// /// Borrows shared projections and parameters so that multiple lowering /// passes (policy, protection) accumulate into the same patch. -pub(crate) struct PolicyTranslationUnit<'parent, 'query, A: Allocator> { - pub projections: &'parent mut AuxiliaryProjections<'query>, +pub(crate) struct PolicyTranslationUnit<'parent, A: Allocator> { + pub projections: &'parent mut AuxiliaryProjections, pub parameters: &'parent mut AuxiliaryParameters, pub actor_id: Option, - pub alloc: A, } -impl PolicyTranslationUnit<'_, '_, A> { +impl PolicyTranslationUnit<'_, A> { pub(crate) fn transpile( &mut self, vertex_type: VertexType, diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection.rs index c3a7c3b595c..cc32771fd06 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection.rs @@ -20,7 +20,7 @@ use crate::postgres::{parameters::AuxiliaryParameters, projections::AuxiliaryPro /// Resolves a query path to its backing column. fn resolve_path( - unit: &mut ProtectionTranslationUnit<'_, '_, A>, + unit: &mut ProtectionTranslationUnit<'_, A>, path: PropertyFilterEntityQueryPath, ) -> Expression { match path { @@ -41,7 +41,7 @@ fn resolve_path( /// Resolves a filter operand to a SQL expression. fn resolve_expression( - unit: &mut ProtectionTranslationUnit<'_, '_, A>, + unit: &mut ProtectionTranslationUnit<'_, A>, expression: &PropertyFilterExpression<'_>, ) -> Expression { match expression { @@ -75,7 +75,7 @@ fn resolve_expression( /// Lowers a [`PropertyFilter`] condition tree into a SQL boolean expression. fn lower_filter( - unit: &mut ProtectionTranslationUnit<'_, '_, A>, + unit: &mut ProtectionTranslationUnit<'_, A>, filter: &PropertyFilter<'_>, ) -> Expression { match filter { @@ -128,7 +128,7 @@ fn lower_filter( /// when the condition holds (property should be masked), or an empty array /// otherwise. fn lower_property_filter( - unit: &mut ProtectionTranslationUnit<'_, '_, A>, + unit: &mut ProtectionTranslationUnit<'_, A>, property_url: BaseUrl, filter: &PropertyFilter<'_>, ) -> Expression { @@ -155,7 +155,7 @@ fn lower_property_filter( /// /// When present, this expression evaluates to a `text[]` of property base URLs /// that should be stripped. It is subtracted from the `properties` and -/// `property_metadata` columns inside the entity_editions LATERAL subquery. +/// `property_metadata` columns inside the `entity_editions` LATERAL subquery. pub(super) struct ProtectionTranslation { /// `NULL` when no properties are protected (no masking needed). pub keys_to_remove: Option, @@ -165,22 +165,19 @@ pub(super) struct ProtectionTranslation { /// /// Borrows shared projections and parameters so that multiple lowering /// passes (policy, protection) accumulate into the same patch. -pub(crate) struct ProtectionTranslationUnit<'parent, 'query, A: Allocator> { - pub projections: &'parent mut AuxiliaryProjections<'query>, +pub(crate) struct ProtectionTranslationUnit<'parent, A: Allocator> { + pub projections: &'parent mut AuxiliaryProjections, pub parameters: &'parent mut AuxiliaryParameters, pub actor_id: Option, } -impl ProtectionTranslationUnit<'_, '_, A> { +impl ProtectionTranslationUnit<'_, A> { pub(crate) fn transpile( &mut self, policy: &PolicyComponents, config: &PropertyProtectionFilterConfig<'_>, ) -> ProtectionTranslation { - if config.is_empty() - || policy.is_instance_admin() - || self.projections.entity_edition_reference().is_none() - { + if config.is_empty() || policy.is_instance_admin() { return ProtectionTranslation { keys_to_remove: None, }; diff --git a/libs/@local/hashql/eval/src/postgres/mod.rs b/libs/@local/hashql/eval/src/postgres/mod.rs index 85728975424..c888418ade8 100644 --- a/libs/@local/hashql/eval/src/postgres/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/mod.rs @@ -55,14 +55,18 @@ use hashql_mir::{ }, }; -use self::{ - continuation::ContinuationColumn, filter::GraphReadFilterCompiler, - parameters::AuxiliaryParameters, projections::Projections, types::traverse_struct, -}; pub use self::{ + authorization::AuthorizationPatch, continuation::ContinuationField, parameters::{Parameter, ParameterIndex, ParameterValue, Parameters, TemporalAxis}, - prepared::{PreparedQueries, PreparedQuery, PreparedQueryPatch}, + prepared::{ + PatchPreparedQuery, PatchPreparedQueryLayer, PreparedQueries, PreparedQuery, + PreparedQueryPatch, + }, +}; +use self::{ + continuation::ContinuationColumn, filter::GraphReadFilterCompiler, + parameters::AuxiliaryParameters, projections::Projections, types::traverse_struct, }; use crate::context::CodeGenerationContext; diff --git a/libs/@local/hashql/eval/src/postgres/parameters.rs b/libs/@local/hashql/eval/src/postgres/parameters.rs index 6e749b0fdcd..061c57a6c0c 100644 --- a/libs/@local/hashql/eval/src/postgres/parameters.rs +++ b/libs/@local/hashql/eval/src/postgres/parameters.rs @@ -252,7 +252,7 @@ impl fmt::Display for Parameters<'_, A> { /// Runtime parameter values for authorization conditions, indexed after /// the compiled parameters (`$K+1..`). -pub struct AuxiliaryParameters { +pub(crate) struct AuxiliaryParameters { initial_offset: usize, parameters: Vec, A>, } diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs index efeae3e1545..72a6d9e9de2 100644 --- a/libs/@local/hashql/eval/src/postgres/prepared.rs +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -2,16 +2,15 @@ clippy::field_scoped_visibility_modifiers, reason = "internal module that is opaque to the outside" )] -use core::{alloc::Allocator, fmt::Display}; +use core::{alloc::Allocator, fmt::Display, marker::PhantomData}; use hash_graph_postgres_store::store::postgres::query::{SelectStatement, Transpile as _}; -use hashql_core::{heap::BumpAllocator, id::Id as _}; +use hashql_core::id::Id as _; use hashql_mir::{ body::basic_block::BasicBlockId, def::{DefId, DefIdSlice}, pass::execution::VertexType, }; -use postgres_types::ToSql; use super::{ ColumnDescriptor, Parameters, @@ -38,41 +37,47 @@ impl PreparedQuery<'_, A> { pub fn transpile(&self) -> impl Display { core::fmt::from_fn(|fmt| self.statement.transpile(fmt)) } - - pub(crate) const fn projections(&self) -> &Projections { - &self.projections - } } -struct HCons { +pub struct HCons { head: H, tail: T, } struct HNil; -pub struct PatchContext<'base, A: Allocator> { - pub projections: AuxiliaryProjections<'base>, +pub struct PatchContext<'ctx, A: Allocator> { + pub projections: AuxiliaryProjections, pub alloc: A, + _marker: PhantomData<&'ctx ()>, } -trait PatchPreparedQuery { +pub trait PatchPreparedQuery { fn patch_query( &mut self, context: &mut PatchContext<'_, A>, query: &mut PreparedQuery<'_, A>, scratch: S, - cont: impl FnOnce(&mut PatchContext<'_, A>, &mut PreparedQuery<'_, A>, S), ); } +pub trait PatchPreparedQueryLayer { + fn patch_query( + &mut self, + context: &mut PatchContext<'_, A>, + query: &mut PreparedQuery<'_, A>, + scratch: S, + next: &mut N, + ) where + N: PatchPreparedQuery; +} + impl PatchPreparedQuery for HNil { fn patch_query( &mut self, context: &mut PatchContext<'_, A>, query: &mut PreparedQuery<'_, A>, - scratch: S, - _: impl FnOnce(&mut PatchContext<'_, A>, &mut PreparedQuery<'_, A>, S), + _: S, ) { let from = query .statement @@ -86,7 +91,7 @@ impl PatchPreparedQuery for HNil { impl PatchPreparedQuery for HCons where - H: PatchPreparedQuery, + H: PatchPreparedQueryLayer, T: PatchPreparedQuery, { fn patch_query( @@ -94,12 +99,10 @@ where context: &mut PatchContext<'_, A>, query: &mut PreparedQuery<'_, A>, scratch: S, - cont: impl FnOnce(&mut PatchContext<'_, A>, &mut PreparedQuery<'_, A>, S), ) { - self.head - .patch_query(context, query, scratch, |context, query, scratch| { - self.tail.patch_query(context, query, scratch, cont) - }); + let Self { head, tail } = self; + + head.patch_query(context, query, scratch, tail); } } @@ -108,7 +111,8 @@ pub struct PreparedQueryPatch { } impl PreparedQueryPatch { - fn new() -> Self { + #[must_use] + pub const fn new() -> Self { Self { patches: HNil } } } @@ -132,11 +136,20 @@ impl PreparedQueryPatch { { let alloc = query.columns.allocator().clone(); - let mut projections = AuxiliaryProjections::new(&query.projections); - let mut context = PatchContext { projections, alloc }; + let projections = AuxiliaryProjections::new(&query.projections); + let mut context = PatchContext { + projections, + alloc, + _marker: PhantomData, + }; + + self.patches.patch_query(&mut context, query, scratch); + } +} - self.patches - .patch_query(&mut context, query, scratch, |_, _, _| {}); +impl Default for PreparedQueryPatch { + fn default() -> Self { + Self::new() } } diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index ca76a26c545..3b9838554e3 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -62,6 +62,10 @@ impl Projections { } } + pub(crate) const fn snapshot(&self) -> Self { + Self { ..*self } + } + const fn next_alias(index: &mut usize) -> Alias { let alias = Alias { condition_index: 0, @@ -507,22 +511,20 @@ impl Projections { /// /// Accessors reuse joins from the base [`Projections`] when available, falling /// back to fresh joins compiled by [`build_joins`](Self::build_joins). -pub struct AuxiliaryProjections<'base> { +pub struct AuxiliaryProjections { index: usize, - base: &'base Projections, + base: Projections, pub entity_ids: Option, - pub entity_editions: Option, pub entity_is_of_type_ids: Option, } -impl<'base> AuxiliaryProjections<'base> { - pub(crate) const fn new(base: &'base Projections) -> Self { +impl AuxiliaryProjections { + pub(crate) const fn new(base: &Projections) -> Self { Self { index: base.index, - base, + base: base.snapshot(), entity_ids: None, - entity_editions: None, entity_is_of_type_ids: None, } } @@ -538,7 +540,10 @@ impl<'base> AuxiliaryProjections<'base> { } pub(crate) const fn snapshot(&self) -> Self { - Self { ..*self } + Self { + base: self.base.snapshot(), + ..*self + } } pub(crate) fn temporal_metadata(&self) -> TableReference<'static> { @@ -564,33 +569,8 @@ impl<'base> AuxiliaryProjections<'base> { } } - /// Entity edition data, joined on `edition_id`. - pub(crate) fn entity_editions(&mut self) -> TableReference<'static> { - let alias = if let Some(base_alias) = self.base.entity_editions { - base_alias - } else if let Some(alias) = self.entity_editions { - alias - } else { - let alias = self.next_alias(); - self.entity_editions = Some(alias); - alias - }; - - TableReference { - schema: None, - name: TableName::from(Table::EntityEditions), - alias: Some(alias), - } - } - - pub(crate) fn entity_edition_reference(&self) -> Option> { - let alias = self.base.entity_editions.or(self.entity_editions)?; - - Some(TableReference { - schema: None, - name: TableName::from(Table::EntityEditions), - alias: Some(alias), - }) + pub(crate) const fn entity_edition_alias(&self) -> Option { + self.base.entity_editions } /// Entity type assignments, joined on `entity_edition_id`. @@ -619,10 +599,6 @@ impl<'base> AuxiliaryProjections<'base> { from = self.base.build_entity_ids(from, alias); } - if let Some(alias) = self.entity_editions { - from = self.base.build_entity_editions(from, alias); - } - if let Some(alias) = self.entity_is_of_type_ids { let fk = ForeignKeyReference::Single { on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), From c84cf9e1ae2a32f007b89c30686d9193d0b12853 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:10:59 +0200 Subject: [PATCH 14/34] fix: lints --- libs/@local/hashql/eval/src/postgres/authorization/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index ec64255d9a1..ea938c73963 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -168,7 +168,10 @@ impl PatchPreparedQueryLayer || alias.as_ref() == table::EntityEditions::PropertyMetadata.as_str() { let base = mem::replace(expression, Expression::Parameter(0)); - *expression = Expression::subtract(base, keys_to_remove.clone()); + + // Group the result of the subtraction so that subsequent operators bind to the + // result, and not to one of it's parts. + *expression = Expression::subtract(base, keys_to_remove.clone()).grouped(); } } } From 9ae64de41d1e2b93c6302267470f489ba0c823f9 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:45:34 +0200 Subject: [PATCH 15/34] feat: checkpoint --- .../query/expression/table_reference.rs | 7 ++ .../eval/src/postgres/authorization/mod.rs | 14 ++-- .../hashql/eval/src/postgres/projections.rs | 70 ++++++++++++++----- 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/libs/@local/graph/postgres-store/src/store/postgres/query/expression/table_reference.rs b/libs/@local/graph/postgres-store/src/store/postgres/query/expression/table_reference.rs index d657db4ce7b..8379c94f8f4 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/query/expression/table_reference.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/query/expression/table_reference.rs @@ -79,6 +79,13 @@ impl Hash for TableNameImpl<'_> { #[derive(Clone, PartialEq, Eq, Hash)] pub struct TableName<'name>(TableNameImpl<'name>); +impl TableName<'_> { + #[must_use] + pub const fn from_table(name: Table) -> Self { + Self(TableNameImpl::Static(name)) + } +} + impl fmt::Debug for TableName<'_> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { self.transpile(fmt) diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index ea938c73963..d1b89b3ba83 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -115,6 +115,13 @@ impl PatchPreparedQueryLayer policy.transpile(query.vertex_type, self.policy, &scratch); query.statement.where_expression.add_condition(condition); + next.patch_query(context, query, scratch); + + let Some(entity_edition_alias) = context.projections.entity_edition_alias() else { + // we remove the keys _last_, and only iff the entity_editions table is still requested + return; + }; + let mut protection = ProtectionTranslationUnit { projections: &mut context.projections, parameters: &mut query.auxiliary_parameters, @@ -124,17 +131,10 @@ impl PatchPreparedQueryLayer let ProtectionTranslation { keys_to_remove } = protection.transpile(self.policy, self.properties); - next.patch_query(context, query, scratch); - let Some(keys_to_remove) = keys_to_remove else { return; }; - let Some(entity_edition_alias) = context.projections.entity_edition_alias() else { - // we remove the keys _last_, and only iff the entity_editions table is still requested - return; - }; - let from = query .statement .from diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index 3b9838554e3..7f6fa9674fc 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -2,7 +2,7 @@ //! //! See [`Projections`] for the main entry point. -use core::alloc::Allocator; +use core::{alloc::Allocator, mem}; use hash_graph_postgres_store::store::postgres::query::{ self, Alias, Column, ColumnName, ColumnReference, ForeignKeyReference, FromItem, Identifier, @@ -172,11 +172,6 @@ impl Projections { let mut from = base; - // entity_editions ON edition_id (INNER) - if let Some(alias) = self.entity_editions { - from = self.build_entity_editions(from, alias); - } - // entity_ids ON (web_id, entity_uuid) (INNER) if let Some(alias) = self.entity_ids { from = self.build_entity_ids(from, alias); @@ -210,6 +205,11 @@ impl Projections { from = self.build_entity_has_right_entity(from, alias); } + // CROSS JOIN LATERAL entity_editions ON edition_id (INNER) + if let Some(alias) = self.entity_editions { + from = self.build_entity_editions(from, alias); + } + // CROSS JOIN LATERALs for continuation subqueries (must come after // all regular joins since they may reference any of the joined tables) for lateral in laterals { @@ -222,11 +222,11 @@ impl Projections { /// Builds `entity_editions` as a LATERAL subquery with explicit column projections. /// /// ```sql - /// INNER JOIN LATERAL ( + /// CROSS JOIN LATERAL ( /// SELECT ee. AS , ... /// FROM entity_editions AS ee /// WHERE ee.edition_id = base.edition_id - /// ) AS ON TRUE + /// ) AS /// ``` /// /// The explicit projections let the authorization graft locate and replace @@ -302,12 +302,8 @@ impl Projections { column_alias: vec![], }; - // INNER JOIN LATERAL (...) ON TRUE - from.join(JoinType::Inner, lateral) - .on(vec![query::Expression::Constant(query::Constant::Boolean( - true, - ))]) - .build() + // CROSS JOIN LATERAL (...) + from.cross_join(lateral) } pub(crate) fn build_entity_ids<'item>( @@ -593,10 +589,47 @@ impl AuxiliaryProjections { } } - /// Appends joins for tables not already present in the base projections. + /// Appends authorization joins before the LATERAL subqueries. + /// + /// The compiled FROM tree ends with a chain of `CROSS JOIN LATERAL` nodes + /// (`entity_editions`, then continuations). Authorization joins must appear + /// before these so that the LATERAL subqueries can reference them. + /// + /// This traverses the right spine of `CrossJoin` nodes to find the + /// insertion point (the innermost non-LATERAL join tree), appends + /// authorization joins there, and reassembles the LATERAL chain on top. pub(crate) fn build_joins(&self, mut from: FromItem<'static>) -> FromItem<'static> { + // This value has no semantic purpose, but is simply a value that we can construct in a + // constant environment and which is cheap. The value will never end up inside of the from + // clause, only when it panics, but all bets are off then anyway. + const SENTINEL: FromItem<'static> = FromItem::Table { + only: true, + table: TableReference { + schema: None, + name: TableName::from_table(table::Table::OntologyIds), + alias: None, + }, + alias: None, + column_alias: Vec::new(), + tablesample: None, + }; + + if self.entity_ids.is_none() && self.entity_is_of_type_ids.is_none() { + return from; + } + + // Walk down the left spine of CrossJoin nodes to find the + // regular join tree underneath the LATERAL chain. + let mut inner = &mut from; + while let FromItem::CrossJoin { left, right: _ } = inner { + inner = left; + } + + // Temporarily swap the core out so we can append joins to it. + let mut core = mem::replace(inner, SENTINEL); + if let Some(alias) = self.entity_ids { - from = self.base.build_entity_ids(from, alias); + core = self.base.build_entity_ids(core, alias); } if let Some(alias) = self.entity_is_of_type_ids { @@ -606,7 +639,7 @@ impl AuxiliaryProjections { join_type: JoinType::Inner, }; - from = from + core = core .join( JoinType::Inner, FromItem::table(Table::EntityIsOfTypeIds) @@ -616,6 +649,9 @@ impl AuxiliaryProjections { .build(); } + // Put the augmented core back; the LATERAL chain above is untouched. + *inner = core; + from } } From 38230807ca5d9bfc829713cb92b69bee10b6be16 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:52:11 +0200 Subject: [PATCH 16/34] feat: checkpoint --- .../eval/src/postgres/authorization/mod.rs | 39 ++++++++++++------- .../eval/src/postgres/authorization/policy.rs | 13 ++++++- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index d1b89b3ba83..1d2c45cfcf7 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -17,7 +17,7 @@ use hash_graph_store::filter::protection::PropertyProtectionFilterConfig; use self::{ policy::{PolicyTranslation, PolicyTranslationUnit}, - protection::{ProtectionTranslation, ProtectionTranslationUnit}, + protection::ProtectionTranslationUnit, }; use super::{PatchPreparedQueryLayer, prepared::PatchContext}; @@ -60,7 +60,11 @@ fn find_from_by_alias<'from, 'id>( join_type: _, right, condition: _, - } => find_from_by_alias(left, needle).or_else(|| find_from_by_alias(right, needle)), + } => { + // right biased, that way we're faster in finding our goal, as our tree is left-heavy, + // with leaves being on the right side. + find_from_by_alias(right, needle).or_else(|| find_from_by_alias(left, needle)) + } FromItem::JoinUsing { left: _, join_type: _, @@ -115,23 +119,28 @@ impl PatchPreparedQueryLayer policy.transpile(query.vertex_type, self.policy, &scratch); query.statement.where_expression.add_condition(condition); - next.patch_query(context, query, scratch); + // Lower protection BEFORE join materialization so its join demands + // (e.g. entity_is_of_type_ids for TypeBaseUrls) are registered. + // The resulting mask expression is grafted AFTER joins are built. + let entity_edition_alias = context.projections.entity_edition_alias(); - let Some(entity_edition_alias) = context.projections.entity_edition_alias() else { - // we remove the keys _last_, and only iff the entity_editions table is still requested - return; - }; + let keys_to_remove = entity_edition_alias.and_then(|_| { + let mut protection = ProtectionTranslationUnit { + projections: &mut context.projections, + parameters: &mut query.auxiliary_parameters, + actor_id: self.policy.actor_id(), + }; - let mut protection = ProtectionTranslationUnit { - projections: &mut context.projections, - parameters: &mut query.auxiliary_parameters, - actor_id: self.policy.actor_id(), - }; + protection + .transpile(self.policy, self.properties) + .keys_to_remove + }); - let ProtectionTranslation { keys_to_remove } = - protection.transpile(self.policy, self.properties); + next.patch_query(context, query, scratch); - let Some(keys_to_remove) = keys_to_remove else { + let Some((entity_edition_alias, keys_to_remove)) = + Option::zip(entity_edition_alias, keys_to_remove) + else { return; }; diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs index ddd1e2fd01d..7b9c5c967e4 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy.rs @@ -354,6 +354,15 @@ impl PolicyTranslationUnit<'_, A> { } let permits = permits.map(Expression::any); + // No permits means deny-all regardless of forbids. + // Return early to avoid lowering forbid constraints (which would + // push params and register joins that nothing references). + if !blank_permit && permits.is_none() { + return PolicyTranslation { + condition: Expression::Constant(Constant::Boolean(false)), + }; + } + let forbids = Some(forbid_constraints) .filter(|constraints| !constraints.is_empty()) .map(|constraints| { @@ -369,12 +378,12 @@ impl PolicyTranslationUnit<'_, A> { (true, _, None) => Expression::Constant(Constant::Boolean(true)), // blank permit + forbids: allow everything except forbidden (true, _, Some(forbids)) => forbids.not(), - // no permits at all: deny all - (false, None, _) => Expression::Constant(Constant::Boolean(false)), // constrained permits only (false, Some(permits), None) => permits, // constrained permits + forbids (false, Some(permits), Some(forbids)) => Expression::all(vec![permits, forbids.not()]), + // unreachable: handled by early return above + (false, None, _) => unreachable!("no-permit case returned early"), }; PolicyTranslation { From 25a778066e039e40709f6db5f2dfbe060e011d64 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:48:01 +0200 Subject: [PATCH 17/34] feat: test infrastructure --- .../eval/src/postgres/authorization/mod.rs | 2 + .../{policy.rs => policy/mod.rs} | 3 + .../postgres/authorization/policy/tests.rs | 478 ++++++++++++++++++ .../{protection.rs => protection/mod.rs} | 3 + .../authorization/protection/tests.rs | 239 +++++++++ .../eval/src/postgres/authorization/tests.rs | 366 ++++++++++++++ .../hashql/eval/src/postgres/projections.rs | 189 +++++++ .../ui/postgres/authorization/.spec.toml | 2 + .../algebra_blank_permit_with_forbids.snap | 6 + .../algebra_constrained_permits_only.snap | 6 + .../policy/algebra_permits_and_forbids.snap | 6 + .../constraint_any_with_type_filter.snap | 6 + .../policy/constraint_exact_entity.snap | 6 + .../authorization/policy/constraint_web.snap | 6 + .../constraint_web_with_created_by.snap | 6 + .../created_by_principal_anonymous.snap | 6 + .../created_by_principal_with_actor.snap | 6 + .../policy/filter_all_conjunction.snap | 6 + .../policy/filter_any_disjunction.snap | 6 + .../policy/filter_not_negation.snap | 6 + .../policy/is_of_base_type_any.snap | 6 + .../policy/is_of_type_overlap.snap | 6 + .../policy/optimize_multiple_entities.snap | 6 + .../policy/optimize_single_entity.snap | 6 + .../policy/optimize_single_web.snap | 6 + .../projections/build_from_base_only.snap | 6 + ...ld_from_editions_before_continuations.snap | 11 + .../build_from_with_entity_editions.snap | 9 + ...ild_from_with_entity_ids_and_editions.snap | 12 + .../build_joins_inserts_before_laterals.snap | 12 + .../build_joins_no_laterals_with_auth.snap | 11 + .../protection/lower_filter_equal.snap | 6 + .../protection/lower_filter_in.snap | 6 + .../protection/lower_filter_nested_all.snap | 6 + .../protection/lower_filter_not_equal.snap | 6 + .../lower_property_filter_case_when.snap | 6 + .../resolve_expression_actor_id.snap | 6 + .../protection/resolve_expression_text.snap | 6 + .../resolve_path_type_base_urls.snap | 6 + .../protection/resolve_path_uuid.snap | 6 + .../protection/transpile_hash_default.snap | 6 + presentation.html | 413 +++++++++++++++ 42 files changed, 1918 insertions(+) rename libs/@local/hashql/eval/src/postgres/authorization/{policy.rs => policy/mod.rs} (99%) create mode 100644 libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs rename libs/@local/hashql/eval/src/postgres/authorization/{protection.rs => protection/mod.rs} (99%) create mode 100644 libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs create mode 100644 libs/@local/hashql/eval/src/postgres/authorization/tests.rs create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/.spec.toml create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_base_only.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_editions_before_continuations.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_editions.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_ids_and_editions.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap create mode 100644 presentation.html diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index 1d2c45cfcf7..952117ebf92 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -23,6 +23,8 @@ use super::{PatchPreparedQueryLayer, prepared::PatchContext}; mod policy; mod protection; +#[cfg(test)] +mod tests; fn find_from_by_alias<'from, 'id>( from: &'from mut FromItem<'id>, diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs similarity index 99% rename from libs/@local/hashql/eval/src/postgres/authorization/policy.rs rename to libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs index 7b9c5c967e4..2c4d1cc42c0 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs @@ -19,6 +19,9 @@ use type_system::{ use crate::postgres::{parameters::AuxiliaryParameters, projections::AuxiliaryProjections}; +#[cfg(test)] +mod tests; + /// Checks whether the entity has a specific `(base_url, version)` type pair. /// /// ```sql diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs new file mode 100644 index 00000000000..da84163eb5c --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs @@ -0,0 +1,478 @@ +use alloc::alloc::Global; +use std::path::PathBuf; + +use hash_graph_authorization::policies::{ + OptimizationData, + resource::{ + EntityResourceConstraint, EntityResourceFilter, EntityTypeResourceConstraint, + EntityTypeResourceFilter, ResourceConstraint, + }, +}; +use hash_graph_postgres_store::store::postgres::query::Transpile as _; +use hashql_mir::pass::execution::VertexType; +use insta::{Settings, assert_snapshot}; +use type_system::{ + knowledge::entity::id::EntityUuid, + ontology::BaseUrl, + principal::{ + actor::{ActorId, UserId}, + actor_group::WebId, + }, +}; + +use super::{ + convert_created_by_principal, convert_entity_resource_filter, convert_is_of_base_type, + convert_is_of_type, convert_resource_constraint, optimize, +}; +use crate::postgres::authorization::tests::{ + ACTOR_UUID, ENTITY_UUID_1, ENTITY_UUID_2, Fixture, WEB_UUID_1, forbid, make_url, permit, + policy_components, +}; + +fn snapshot_settings() -> Settings { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut settings = Settings::clone_current(); + settings.set_snapshot_path(manifest_dir.join("tests/ui/postgres/authorization/policy")); + settings.set_prepend_module_to_snapshot(false); + settings +} + +#[test] +fn is_of_type_overlap() { + let mut fixture = Fixture::new(); + let url = make_url("https://hash.ai/@h/types/entity-type/machine/", 1); + let description = format!("{url:?}"); + let expr = convert_is_of_type(&mut fixture.policy(), url); + + let mut settings = snapshot_settings(); + settings.set_description(description); + let _guard = settings.bind_to_scope(); + assert_snapshot!("is_of_type_overlap", expr.transpile_to_string()); +} + +#[test] +fn is_of_base_type_any() { + let mut fixture = Fixture::new(); + let base = BaseUrl::new("https://hash.ai/@h/types/entity-type/machine/".to_owned()) + .expect("valid base URL"); + let description = format!("{base:?}"); + let expr = convert_is_of_base_type(&mut fixture.policy(), base); + + let mut settings = snapshot_settings(); + settings.set_description(description); + let _guard = settings.bind_to_scope(); + assert_snapshot!("is_of_base_type_any", expr.transpile_to_string()); +} + +#[test] +fn created_by_principal_with_actor() { + let mut fixture = Fixture::new(); + let expr = convert_created_by_principal(&mut fixture.policy()); + + let mut settings = snapshot_settings(); + settings.set_description(format!( + "CreatedByPrincipal, actor = {:?}", + fixture.policy().actor_id + )); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "created_by_principal_with_actor", + expr.transpile_to_string() + ); +} + +#[test] +fn created_by_principal_anonymous() { + let mut fixture = Fixture::new(); + let expr = convert_created_by_principal(&mut fixture.policy_anon()); + + let mut settings = snapshot_settings(); + settings.set_description(format!( + "CreatedByPrincipal, actor = {:?}", + fixture.policy_anon().actor_id + )); + let _guard = settings.bind_to_scope(); + assert_snapshot!("created_by_principal_anonymous", expr.transpile_to_string()); +} + +#[test] +fn constraint_exact_entity() { + let mut fixture = Fixture::new(); + let constraint = ResourceConstraint::Entity(EntityResourceConstraint::Exact { + id: EntityUuid::new(ENTITY_UUID_1), + }); + let expr = convert_resource_constraint(&mut fixture.policy(), &constraint); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{constraint:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("constraint_exact_entity", expr.transpile_to_string()); +} + +#[test] +fn constraint_web() { + let mut fixture = Fixture::new(); + let constraint = ResourceConstraint::Web { + web_id: WebId::new(WEB_UUID_1), + }; + let expr = convert_resource_constraint(&mut fixture.policy(), &constraint); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{constraint:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("constraint_web", expr.transpile_to_string()); +} + +#[test] +fn constraint_any_with_type_filter() { + let mut fixture = Fixture::new(); + let constraint = ResourceConstraint::Entity(EntityResourceConstraint::Any { + filter: EntityResourceFilter::IsOfType { + entity_type: make_url("https://hash.ai/@h/types/entity-type/user/", 1), + }, + }); + let expr = convert_resource_constraint(&mut fixture.policy(), &constraint); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{constraint:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "constraint_any_with_type_filter", + expr.transpile_to_string() + ); +} + +#[test] +fn constraint_web_with_created_by() { + let mut fixture = Fixture::new(); + let constraint = ResourceConstraint::Entity(EntityResourceConstraint::Web { + web_id: WebId::new(WEB_UUID_1), + filter: EntityResourceFilter::CreatedByPrincipal, + }); + let expr = convert_resource_constraint(&mut fixture.policy(), &constraint); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{constraint:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("constraint_web_with_created_by", expr.transpile_to_string()); +} + +#[test] +fn constraint_non_entity_is_false() { + let mut fixture = Fixture::new(); + let constraint = ResourceConstraint::EntityType(EntityTypeResourceConstraint::Any { + filter: EntityTypeResourceFilter::All { filters: vec![] }, + }); + let expr = convert_resource_constraint(&mut fixture.policy(), &constraint); + assert_eq!(expr.transpile_to_string(), "FALSE"); +} + +#[test] +fn filter_all_conjunction() { + let mut fixture = Fixture::new(); + let filter = EntityResourceFilter::All { + filters: vec![ + EntityResourceFilter::CreatedByPrincipal, + EntityResourceFilter::IsOfBaseType { + entity_type: BaseUrl::new("https://hash.ai/@h/types/entity-type/user/".to_owned()) + .expect("valid base URL"), + }, + ], + }; + let expr = convert_entity_resource_filter(&mut fixture.policy(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("filter_all_conjunction", expr.transpile_to_string()); +} + +#[test] +fn filter_any_disjunction() { + let mut fixture = Fixture::new(); + let filter = EntityResourceFilter::Any { + filters: vec![ + EntityResourceFilter::IsOfType { + entity_type: make_url("https://hash.ai/@h/types/entity-type/user/", 1), + }, + EntityResourceFilter::IsOfType { + entity_type: make_url("https://hash.ai/@h/types/entity-type/machine/", 2), + }, + ], + }; + let expr = convert_entity_resource_filter(&mut fixture.policy(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("filter_any_disjunction", expr.transpile_to_string()); +} + +#[test] +fn filter_not_negation() { + let mut fixture = Fixture::new(); + let filter = EntityResourceFilter::Not { + filter: Box::new(EntityResourceFilter::CreatedByPrincipal), + }; + let expr = convert_entity_resource_filter(&mut fixture.policy(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("filter_not_negation", expr.transpile_to_string()); +} + +#[test] +fn optimize_single_entity_uuid() { + let mut fixture = Fixture::new(); + let mut unit = fixture.policy(); + let mut permits = None; + let data = OptimizationData { + permitted_entity_uuids: vec![EntityUuid::new(ENTITY_UUID_1)], + permitted_entity_type_uuids: vec![], + permitted_property_type_uuids: vec![], + permitted_data_type_uuids: vec![], + permitted_web_ids: vec![], + }; + optimize(&mut unit, &mut permits, &data); + let expr = permits.expect("should have a permit expression"); + assert_eq!(expr.len(), 1); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{data:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("optimize_single_entity", expr[0].transpile_to_string()); +} + +#[test] +fn optimize_multiple_entity_uuids() { + let mut fixture = Fixture::new(); + let mut unit = fixture.policy(); + let mut permits = None; + let data = OptimizationData { + permitted_entity_uuids: vec![ + EntityUuid::new(ENTITY_UUID_1), + EntityUuid::new(ENTITY_UUID_2), + ], + permitted_entity_type_uuids: vec![], + permitted_property_type_uuids: vec![], + permitted_data_type_uuids: vec![], + permitted_web_ids: vec![], + }; + optimize(&mut unit, &mut permits, &data); + let expr = permits.expect("should have a permit expression"); + assert_eq!(expr.len(), 1); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{data:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("optimize_multiple_entities", expr[0].transpile_to_string()); +} + +#[test] +fn optimize_single_web_id() { + let mut fixture = Fixture::new(); + let mut unit = fixture.policy(); + let mut permits = None; + let data = OptimizationData { + permitted_entity_uuids: vec![], + permitted_entity_type_uuids: vec![], + permitted_property_type_uuids: vec![], + permitted_data_type_uuids: vec![], + permitted_web_ids: vec![WebId::new(WEB_UUID_1)], + }; + optimize(&mut unit, &mut permits, &data); + let expr = permits.expect("should have a permit expression"); + assert_eq!(expr.len(), 1); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{data:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("optimize_single_web", expr[0].transpile_to_string()); +} + +#[test] +fn optimize_empty_is_noop() { + let mut fixture = Fixture::new(); + let mut unit = fixture.policy(); + let mut permits = None; + let data = OptimizationData::default(); + optimize(&mut unit, &mut permits, &data); + assert!( + permits.is_none(), + "empty optimization should not add permits" + ); +} + +#[test] +fn algebra_blank_forbid_denies_all() { + let mut fixture = Fixture::new(); + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components(actor, vec![forbid(|| None)]); + let result = fixture + .policy() + .transpile(VertexType::Entity, &policy, Global); + assert_eq!(result.condition.transpile_to_string(), "FALSE"); +} + +#[test] +fn algebra_blank_permit_allows_all() { + let mut fixture = Fixture::new(); + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components(actor, vec![permit(|| None)]); + let result = fixture + .policy() + .transpile(VertexType::Entity, &policy, Global); + assert_eq!(result.condition.transpile_to_string(), "TRUE"); +} + +#[test] +fn algebra_blank_permit_with_forbids() { + let mut fixture = Fixture::new(); + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policies = vec![ + permit(|| None), + forbid(|| { + Some(ResourceConstraint::Web { + web_id: WebId::new(WEB_UUID_1), + }) + }), + ]; + let description = format!( + "{:?}", + policies.iter().map(|policy| policy()).collect::>() + ); + let policy = policy_components(actor, policies); + let result = fixture + .policy() + .transpile(VertexType::Entity, &policy, Global); + + let mut settings = snapshot_settings(); + settings.set_description(description); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "algebra_blank_permit_with_forbids", + result.condition.transpile_to_string() + ); +} + +#[test] +fn algebra_no_permits_denies_all() { + let mut fixture = Fixture::new(); + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components( + actor, + vec![forbid(|| { + Some(ResourceConstraint::Web { + web_id: WebId::new(WEB_UUID_1), + }) + })], + ); + let result = fixture + .policy() + .transpile(VertexType::Entity, &policy, Global); + assert_eq!( + result.condition.transpile_to_string(), + "FALSE", + "forbids without permits should deny all", + ); +} + +#[test] +fn algebra_constrained_permits_only() { + let mut fixture = Fixture::new(); + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policies = vec![ + permit(|| { + Some(ResourceConstraint::Entity( + EntityResourceConstraint::Exact { + id: EntityUuid::new(ENTITY_UUID_1), + }, + )) + }), + permit(|| { + Some(ResourceConstraint::Web { + web_id: WebId::new(WEB_UUID_1), + }) + }), + ]; + let description = format!( + "{:?}", + policies.iter().map(|policy| policy()).collect::>() + ); + let policy = policy_components(actor, policies); + let result = fixture + .policy() + .transpile(VertexType::Entity, &policy, Global); + + let mut settings = snapshot_settings(); + settings.set_description(description); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "algebra_constrained_permits_only", + result.condition.transpile_to_string(), + ); +} + +#[test] +fn algebra_permits_and_forbids() { + let mut fixture = Fixture::new(); + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policies = vec![ + permit(|| { + Some(ResourceConstraint::Entity( + EntityResourceConstraint::Exact { + id: EntityUuid::new(ENTITY_UUID_1), + }, + )) + }), + forbid(|| { + Some(ResourceConstraint::Entity(EntityResourceConstraint::Any { + filter: EntityResourceFilter::IsOfType { + entity_type: make_url("https://hash.ai/@h/types/entity-type/user/", 1), + }, + })) + }), + ]; + let description = format!( + "{:?}", + policies.iter().map(|policy| policy()).collect::>() + ); + let policy = policy_components(actor, policies); + let result = fixture + .policy() + .transpile(VertexType::Entity, &policy, Global); + + let mut settings = snapshot_settings(); + settings.set_description(description); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "algebra_permits_and_forbids", + result.condition.transpile_to_string(), + ); +} + +#[test] +fn algebra_blank_forbid_resets_projections() { + let mut fixture = Fixture::new(); + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components( + actor, + vec![ + permit(|| { + Some(ResourceConstraint::Entity(EntityResourceConstraint::Any { + filter: EntityResourceFilter::CreatedByPrincipal, + })) + }), + forbid(|| None), + ], + ); + let result = fixture + .policy() + .transpile(VertexType::Entity, &policy, Global); + assert_eq!(result.condition.transpile_to_string(), "FALSE"); + assert!( + fixture.projections.entity_ids.is_none(), + "blank forbid should reset auxiliary projections", + ); +} diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection/mod.rs similarity index 99% rename from libs/@local/hashql/eval/src/postgres/authorization/protection.rs rename to libs/@local/hashql/eval/src/postgres/authorization/protection/mod.rs index cc32771fd06..b1e5f26e54a 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection/mod.rs @@ -18,6 +18,9 @@ use type_system::{ use crate::postgres::{parameters::AuxiliaryParameters, projections::AuxiliaryProjections}; +#[cfg(test)] +mod tests; + /// Resolves a query path to its backing column. fn resolve_path( unit: &mut ProtectionTranslationUnit<'_, A>, diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs new file mode 100644 index 00000000000..d5e52cd3b2f --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs @@ -0,0 +1,239 @@ +use alloc::borrow::Cow; +use std::path::PathBuf; + +use hash_graph_postgres_store::store::postgres::query::Transpile as _; +use hash_graph_store::filter::{ + Parameter, + protection::{ + PropertyFilter, PropertyFilterEntityQueryPath, PropertyFilterExpression, + PropertyFilterExpressionList, PropertyProtectionFilterConfig, + }, +}; +use insta::{Settings, assert_snapshot}; +use type_system::ontology::BaseUrl; + +use super::{lower_filter, lower_property_filter, resolve_expression, resolve_path}; +use crate::postgres::authorization::tests::{ + ACTOR_UUID, Fixture, policy_components, policy_components_admin, +}; + +fn base_url(url: &str) -> BaseUrl { + BaseUrl::new(url.to_owned()).expect("valid base URL") +} + +fn snapshot_settings() -> Settings { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut settings = Settings::clone_current(); + settings.set_snapshot_path(manifest_dir.join("tests/ui/postgres/authorization/protection")); + settings.set_prepend_module_to_snapshot(false); + settings +} + +#[test] +fn resolve_path_uuid() { + let mut fixture = Fixture::new(); + let expr = resolve_path( + &mut fixture.protection(), + PropertyFilterEntityQueryPath::Uuid, + ); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{:?}", PropertyFilterEntityQueryPath::Uuid)); + let _guard = settings.bind_to_scope(); + assert_snapshot!("resolve_path_uuid", expr.transpile_to_string()); +} + +#[test] +fn resolve_path_type_base_urls() { + let mut fixture = Fixture::new(); + let expr = resolve_path( + &mut fixture.protection(), + PropertyFilterEntityQueryPath::TypeBaseUrls, + ); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{:?}", PropertyFilterEntityQueryPath::TypeBaseUrls)); + let _guard = settings.bind_to_scope(); + assert_snapshot!("resolve_path_type_base_urls", expr.transpile_to_string()); +} + +#[test] +fn resolve_expression_text_parameter() { + let mut fixture = Fixture::new(); + let param = PropertyFilterExpression::Parameter { + parameter: Parameter::Text(Cow::Borrowed("hello")), + }; + let expr = resolve_expression(&mut fixture.protection(), ¶m); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{param:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("resolve_expression_text", expr.transpile_to_string()); +} + +#[test] +fn resolve_expression_actor_id() { + let mut fixture = Fixture::new(); + let expr = resolve_expression( + &mut fixture.protection(), + &PropertyFilterExpression::ActorId, + ); + + let mut settings = snapshot_settings(); + settings.set_description(format!( + "ActorId, actor = {:?}", + fixture.protection().actor_id + )); + let _guard = settings.bind_to_scope(); + assert_snapshot!("resolve_expression_actor_id", expr.transpile_to_string()); +} + +#[test] +fn lower_filter_equal() { + let mut fixture = Fixture::new(); + let filter = PropertyFilter::Equal( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ); + let expr = lower_filter(&mut fixture.protection(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("lower_filter_equal", expr.transpile_to_string()); +} + +#[test] +fn lower_filter_not_equal() { + let mut fixture = Fixture::new(); + let filter = PropertyFilter::NotEqual( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ); + let expr = lower_filter(&mut fixture.protection(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("lower_filter_not_equal", expr.transpile_to_string()); +} + +#[test] +fn lower_filter_in() { + let mut fixture = Fixture::new(); + let filter = PropertyFilter::In( + PropertyFilterExpression::Parameter { + parameter: Parameter::Text(Cow::Borrowed("https://hash.ai/@h/types/entity-type/user/")), + }, + PropertyFilterExpressionList::Path { + path: PropertyFilterEntityQueryPath::TypeBaseUrls, + }, + ); + let expr = lower_filter(&mut fixture.protection(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("lower_filter_in", expr.transpile_to_string()); +} + +#[test] +fn lower_filter_nested_all_any() { + let mut fixture = Fixture::new(); + let filter = PropertyFilter::All(vec![ + PropertyFilter::In( + PropertyFilterExpression::Parameter { + parameter: Parameter::Text(Cow::Borrowed( + "https://hash.ai/@h/types/entity-type/user/", + )), + }, + PropertyFilterExpressionList::Path { + path: PropertyFilterEntityQueryPath::TypeBaseUrls, + }, + ), + PropertyFilter::NotEqual( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ), + ]); + let expr = lower_filter(&mut fixture.protection(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("lower_filter_nested_all", expr.transpile_to_string()); +} + +#[test] +fn lower_property_filter_case_when() { + let mut fixture = Fixture::new(); + let filter = PropertyFilter::Equal( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ); + let url = base_url("https://hash.ai/@h/types/property-type/email/"); + let expr = lower_property_filter(&mut fixture.protection(), url, &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("property: email/, filter: {filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "lower_property_filter_case_when", + expr.transpile_to_string() + ); +} + +#[test] +fn transpile_empty_config_returns_none() { + let mut fixture = Fixture::new(); + let policy = policy_components(None, vec![]); + let config = PropertyProtectionFilterConfig::new(); + let result = fixture.protection().transpile(&policy, &config); + assert!(result.keys_to_remove.is_none()); +} + +#[test] +fn transpile_instance_admin_returns_none() { + let mut fixture = Fixture::new(); + let actor = Some(type_system::principal::actor::ActorId::User( + type_system::principal::actor::UserId::new(ACTOR_UUID), + )); + let policy = policy_components_admin(actor, vec![]); + let mut config = PropertyProtectionFilterConfig::new(); + config.protect_property( + base_url("https://hash.ai/@h/types/property-type/email/"), + PropertyFilter::Equal( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ), + ); + let result = fixture.protection().transpile(&policy, &config); + assert!(result.keys_to_remove.is_none()); +} + +#[test] +fn transpile_hash_default_config() { + let mut fixture = Fixture::new(); + let actor = Some(type_system::principal::actor::ActorId::User( + type_system::principal::actor::UserId::new(ACTOR_UUID), + )); + let policy = policy_components(actor, vec![]); + let config = PropertyProtectionFilterConfig::hash_default(); + let result = fixture.protection().transpile(&policy, &config); + let expr = result.keys_to_remove.expect("should produce a mask"); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{config:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("transpile_hash_default", expr.transpile_to_string()); +} diff --git a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs new file mode 100644 index 00000000000..edc8361e122 --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs @@ -0,0 +1,366 @@ +//! Mock store for authorization unit tests. +//! +//! Returns pre-configured policies without requiring a database connection. + +use alloc::alloc::Global; +use core::future; +use std::collections::HashSet; + +use error_stack::Report; +use hash_graph_authorization::policies::{ + ContextBuilder, Effect, MergePolicies, Policy, PolicyComponents, PolicyId, ResolvedPolicy, + action::ActionName, + principal::actor::AuthenticatedActor, + resource::{ + DataTypeResource, EntityResource, EntityTypeResource, PropertyTypeResource, + ResourceConstraint, + }, + store::{ + CreateWebParameter, CreateWebResponse, PolicyCreationParams, PolicyFilter, PolicyStore, + PolicyUpdateOperation, PrincipalStore, ResolvePoliciesParams, RoleAssignmentStatus, + RoleUnassignmentStatus, + error::{ + BuildDataTypeContextError, BuildEntityContextError, BuildEntityTypeContextError, + BuildPrincipalContextError, BuildPropertyTypeContextError, CreatePolicyError, + DetermineActorError, EnsureSystemPoliciesError, GetPoliciesError, + GetSystemAccountError, RemovePolicyError, RoleAssignmentError, TeamRoleError, + UpdatePolicyError, WebCreationError, WebRoleError, + }, + }, +}; +use type_system::{ + knowledge::entity::id::EntityEditionId, + ontology::{BaseUrl, VersionedUrl, id::OntologyTypeVersion}, + principal::{ + actor::{ActorEntityUuid, ActorId, MachineId, UserId}, + actor_group::{ActorGroupEntityUuid, ActorGroupId, TeamId, WebId}, + role::{RoleName, TeamRole, TeamRoleId, WebRole, WebRoleId}, + }, +}; +use uuid::Uuid; + +use super::{policy::PolicyTranslationUnit, protection::ProtectionTranslationUnit}; +use crate::postgres::{ + Parameters, + parameters::AuxiliaryParameters, + projections::{AuxiliaryProjections, Projections}, +}; + +pub(crate) struct Fixture { + pub projections: AuxiliaryProjections, + pub parameters: AuxiliaryParameters, +} + +impl Fixture { + pub(crate) fn new() -> Self { + let base = Projections::new(); + let params = Parameters::new_in(Global); + + Self { + projections: AuxiliaryProjections::new(&base), + parameters: AuxiliaryParameters::new(¶ms, Global), + } + } + + pub(crate) fn policy(&mut self) -> PolicyTranslationUnit<'_, Global> { + PolicyTranslationUnit { + projections: &mut self.projections, + parameters: &mut self.parameters, + actor_id: Some(ActorId::User(UserId::new(ACTOR_UUID))), + } + } + + pub(crate) fn policy_anon(&mut self) -> PolicyTranslationUnit<'_, Global> { + PolicyTranslationUnit { + projections: &mut self.projections, + parameters: &mut self.parameters, + actor_id: None, + } + } + + pub(crate) fn protection(&mut self) -> ProtectionTranslationUnit<'_, Global> { + ProtectionTranslationUnit { + projections: &mut self.projections, + parameters: &mut self.parameters, + actor_id: Some(ActorId::User(UserId::new(ACTOR_UUID))), + } + } +} + +/// Returns pre-configured policies for a given actor. +pub(crate) struct MockStore { + pub actor_id: Option, + pub is_instance_admin: bool, + pub policies: Vec, +} + +impl PolicyStore for MockStore +where + F: Fn() -> ResolvedPolicy + Send + Sync, +{ + async fn create_policy( + &mut self, + _: AuthenticatedActor, + _: PolicyCreationParams, + ) -> Result> { + unimplemented!("not needed for authorization expression tests") + } + + async fn get_policy_by_id( + &self, + _: AuthenticatedActor, + _: PolicyId, + ) -> Result, Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn query_policies( + &self, + _: AuthenticatedActor, + _: &PolicyFilter, + ) -> Result, Report> { + unimplemented!("not needed for authorization expression tests") + } + + fn resolve_policies_for_actor( + &self, + _: AuthenticatedActor, + _: ResolvePoliciesParams<'_>, + ) -> impl Future, Report>> { + future::ready(Ok(self.policies.iter().map(|policy| (policy)()).collect())) + } + + async fn update_policy_by_id( + &mut self, + _: AuthenticatedActor, + _: PolicyId, + _: &[PolicyUpdateOperation], + ) -> Result> { + unimplemented!("not needed for authorization expression tests") + } + + async fn archive_policy_by_id( + &mut self, + _: AuthenticatedActor, + _: PolicyId, + ) -> Result<(), Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn delete_policy_by_id( + &mut self, + _: AuthenticatedActor, + _: PolicyId, + ) -> Result<(), Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn seed_system_policies(&mut self) -> Result<(), Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn build_entity_type_context( + &self, + _: &[&VersionedUrl], + ) -> Result>, Report<[BuildEntityTypeContextError]>> { + unimplemented!("not needed for authorization expression tests") + } + + async fn build_property_type_context( + &self, + _: &[&VersionedUrl], + ) -> Result>, Report<[BuildPropertyTypeContextError]>> { + unimplemented!("not needed for authorization expression tests") + } + + async fn build_data_type_context( + &self, + _: &[&VersionedUrl], + ) -> Result>, Report<[BuildDataTypeContextError]>> { + unimplemented!("not needed for authorization expression tests") + } + + async fn build_entity_context( + &self, + _: &[EntityEditionId], + ) -> Result>, Report<[BuildEntityContextError]>> { + unimplemented!("not needed for authorization expression tests") + } +} + +impl PrincipalStore for MockStore +where + F: Send + Sync, +{ + async fn get_or_create_system_machine( + &mut self, + _: &str, + ) -> Result> { + unimplemented!("not needed for authorization expression tests") + } + + async fn create_web( + &mut self, + _: ActorId, + _: CreateWebParameter, + ) -> Result> { + unimplemented!("not needed for authorization expression tests") + } + + async fn get_web_roles( + &mut self, + _: ActorEntityUuid, + _: WebId, + ) -> Result, Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn get_team_roles( + &mut self, + _: ActorEntityUuid, + _: TeamId, + ) -> Result, Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn assign_role( + &mut self, + _: ActorEntityUuid, + _: ActorEntityUuid, + _: ActorGroupEntityUuid, + _: RoleName, + ) -> Result> { + unimplemented!("not needed for authorization expression tests") + } + + async fn get_actor_group_role( + &mut self, + _: ActorEntityUuid, + _: ActorGroupEntityUuid, + ) -> Result, Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn get_role_assignments( + &mut self, + _: ActorGroupEntityUuid, + _: RoleName, + ) -> Result, Report> { + unimplemented!("not needed for authorization expression tests") + } + + async fn unassign_role( + &mut self, + _: ActorEntityUuid, + _: ActorEntityUuid, + _: ActorGroupEntityUuid, + _: RoleName, + ) -> Result> { + unimplemented!("not needed for authorization expression tests") + } + + fn determine_actor( + &self, + _: ActorEntityUuid, + ) -> impl Future, Report>> { + future::ready(Ok(self.actor_id)) + } + + fn build_principal_context( + &self, + _: ActorId, + context_builder: &mut ContextBuilder, + ) -> impl Future>> { + if self.is_instance_admin { + use type_system::principal::actor_group::{ActorGroup, Team}; + + context_builder.add_actor_group(&ActorGroup::Team(Team { + id: TeamId::new(Uuid::nil()), + name: "instance-admins".to_owned(), + parent_id: ActorGroupId::Web(WebId::new(Uuid::nil())), + roles: HashSet::new(), + })); + } + + future::ready(Ok(())) + } +} + +pub(crate) const ACTOR_UUID: Uuid = Uuid::from_u128(0xAAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA_AAAA); +pub(crate) const ENTITY_UUID_1: Uuid = Uuid::from_u128(0x1111_1111_1111_1111_1111_1111_1111_1111); +pub(crate) const ENTITY_UUID_2: Uuid = Uuid::from_u128(0x2222_2222_2222_2222_2222_2222_2222_2222); +pub(crate) const WEB_UUID_1: Uuid = Uuid::from_u128(0x3333_3333_3333_3333_3333_3333_3333_3333); + +pub(crate) fn policy_components( + actor_id: Option, + policies: Vec ResolvedPolicy + Send + Sync>>, +) -> PolicyComponents { + let store = MockStore { + actor_id, + is_instance_admin: false, + policies, + }; + + let actor = actor_id.map_or_else(ActorEntityUuid::public_actor, ActorEntityUuid::from); + + futures_lite::future::block_on( + PolicyComponents::builder(&store) + .with_actor(actor) + .with_action(ActionName::ViewEntity, MergePolicies::Yes) + .into_future(), + ) + .expect("mock store should not fail") +} + +pub(crate) fn permit<'resource>( + resource: impl Fn() -> Option + Send + Sync + 'resource, +) -> Box ResolvedPolicy + Send + Sync + 'resource> { + Box::new(move || ResolvedPolicy { + original_policy_id: PolicyId::new(Uuid::new_v4()), + effect: Effect::Permit, + actions: vec![ActionName::ViewEntity], + resource: (resource)(), + }) +} + +pub(crate) fn forbid<'resource>( + resource: impl Fn() -> Option + Send + Sync + 'resource, +) -> Box ResolvedPolicy + Send + Sync + 'resource> { + Box::new(move || ResolvedPolicy { + original_policy_id: PolicyId::new(Uuid::new_v4()), + effect: Effect::Forbid, + actions: vec![ActionName::ViewEntity], + resource: (resource)(), + }) +} + +pub(crate) fn policy_components_admin( + actor_id: Option, + policies: Vec ResolvedPolicy + Send + Sync>>, +) -> PolicyComponents { + let store = MockStore { + actor_id, + is_instance_admin: true, + policies, + }; + + let actor = actor_id.map_or_else(ActorEntityUuid::public_actor, ActorEntityUuid::from); + + futures_lite::future::block_on( + PolicyComponents::builder(&store) + .with_actor(actor) + .with_action(ActionName::ViewEntity, MergePolicies::Yes) + .into_future(), + ) + .expect("mock store should not fail") +} + +pub(crate) fn make_url(base: &str, version: u32) -> VersionedUrl { + VersionedUrl { + base_url: BaseUrl::new(base.to_owned()).expect("valid base URL"), + version: OntologyTypeVersion { + major: version, + pre_release: None, + }, + } +} diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index 7f6fa9674fc..673bd60447b 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -507,6 +507,7 @@ impl Projections { /// /// Accessors reuse joins from the base [`Projections`] when available, falling /// back to fresh joins compiled by [`build_joins`](Self::build_joins). +#[derive(Debug)] pub struct AuxiliaryProjections { index: usize, base: Projections, @@ -655,3 +656,191 @@ impl AuxiliaryProjections { from } } + +#[cfg(test)] +mod tests { + use alloc::alloc::Global; + use std::path::PathBuf; + + use hash_graph_postgres_store::store::postgres::query::{ + FromItem, SelectStatement, Table, TableName, TableReference, Transpile as _, + }; + use insta::{Settings, assert_snapshot}; + + use super::{AuxiliaryProjections, Projections}; + use crate::postgres::Parameters; + + fn snapshot_settings() -> Settings { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut settings = Settings::clone_current(); + settings + .set_snapshot_path(manifest_dir.join("tests/ui/postgres/authorization/projections")); + settings.set_prepend_module_to_snapshot(false); + settings + } + + fn make_lateral(name: &'static str) -> FromItem<'static> { + FromItem::Subquery { + lateral: true, + statement: Box::new( + SelectStatement::builder() + .selects(vec![]) + .from( + FromItem::table(Table::EntityTemporalMetadata) + .alias(TableReference { + schema: None, + name: TableName::from(name), + alias: None, + }) + .build(), + ) + .build(), + ), + alias: Some(TableReference { + schema: None, + name: TableName::from(name), + alias: None, + }), + column_alias: vec![], + } + } + + #[test] + fn build_from_base_only() { + let projections = Projections::new(); + let mut parameters = Parameters::new_in(Global); + let from = projections.build_from(&mut parameters, Vec::new()); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{projections:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!("build_from_base_only", from.transpile_to_string()); + } + + #[test] + fn build_from_with_entity_editions() { + let mut projections = Projections::new(); + projections.entity_editions(); + let mut parameters = Parameters::new_in(Global); + let from = projections.build_from(&mut parameters, Vec::new()); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{projections:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "build_from_with_entity_editions", + from.transpile_to_string() + ); + } + + #[test] + fn build_from_with_entity_ids_and_editions() { + let mut projections = Projections::new(); + projections.entity_ids(); + projections.entity_editions(); + let mut parameters = Parameters::new_in(Global); + let from = projections.build_from(&mut parameters, Vec::new()); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{projections:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "build_from_with_entity_ids_and_editions", + from.transpile_to_string(), + ); + } + + #[test] + fn build_from_editions_before_continuations() { + let mut projections = Projections::new(); + projections.entity_editions(); + let mut parameters = Parameters::new_in(Global); + + let lateral = make_lateral("continuation_1"); + let from = projections.build_from(&mut parameters, vec![lateral]); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{projections:?}, 1 continuation lateral")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "build_from_editions_before_continuations", + from.transpile_to_string(), + ); + } + + #[test] + fn build_joins_no_laterals_no_auth() { + let base = Projections::new(); + let aux = AuxiliaryProjections::new(&base); + + let from = FromItem::table(Table::EntityTemporalMetadata) + .alias(TableReference { + schema: None, + name: TableName::from(Table::EntityTemporalMetadata), + alias: Some(base.base_alias), + }) + .build(); + + let result = aux.build_joins(from.clone()); + assert_eq!( + result.transpile_to_string(), + from.transpile_to_string(), + "no auth joins requested means FROM is unchanged", + ); + } + + #[test] + fn build_joins_inserts_before_laterals() { + let base = Projections::new(); + let mut aux = AuxiliaryProjections::new(&base); + aux.entity_is_of_type_ids(); + + let core = FromItem::table(Table::EntityTemporalMetadata) + .alias(TableReference { + schema: None, + name: TableName::from(Table::EntityTemporalMetadata), + alias: Some(base.base_alias), + }) + .build(); + + let lateral_1 = make_lateral("continuation_1"); + let lateral_2 = make_lateral("continuation_2"); + let from = core.cross_join(lateral_1).cross_join(lateral_2); + + let result = aux.build_joins(from); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{aux:?}, 2 continuation laterals")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "build_joins_inserts_before_laterals", + result.transpile_to_string(), + ); + } + + #[test] + fn build_joins_no_laterals_with_auth() { + let base = Projections::new(); + let mut aux = AuxiliaryProjections::new(&base); + aux.entity_ids(); + aux.entity_is_of_type_ids(); + + let from = FromItem::table(Table::EntityTemporalMetadata) + .alias(TableReference { + schema: None, + name: TableName::from(Table::EntityTemporalMetadata), + alias: Some(base.base_alias), + }) + .build(); + + let result = aux.build_joins(from); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{aux:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "build_joins_no_laterals_with_auth", + result.transpile_to_string(), + ); + } +} diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/.spec.toml b/libs/@local/hashql/eval/tests/ui/postgres/authorization/.spec.toml new file mode 100644 index 00000000000..56a01cfa866 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/.spec.toml @@ -0,0 +1,2 @@ +skip = true +suite = "eval/postgres-authorization" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap new file mode 100644 index 00000000000..2d766707faa --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "[ResolvedPolicy { original_policy_id: PolicyId(d0ff6345-c5c5-4576-83c6-6694ffe32332), effect: Permit, actions: [ViewEntity], resource: None }, ResolvedPolicy { original_policy_id: PolicyId(412a7e0d-6bc3-478b-86eb-18e0ff70ef14), effect: Forbid, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" +expression: result.condition.transpile_to_string() +--- +NOT(("entity_temporal_metadata_0_0_0"."web_id" = $1)) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap new file mode 100644 index 00000000000..9ea7271cc59 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "[ResolvedPolicy { original_policy_id: PolicyId(f35ab935-b770-4319-a81a-4cbd1d67bbd1), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(38ad2813-8eac-48c5-a2bd-af2ede9e9065), effect: Permit, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" +expression: result.condition.transpile_to_string() +--- +(("entity_temporal_metadata_0_0_0"."entity_uuid" = $1) OR ("entity_temporal_metadata_0_0_0"."web_id" = $2)) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap new file mode 100644 index 00000000000..2fc00726afa --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "[ResolvedPolicy { original_policy_id: PolicyId(c9a801c0-c283-423a-89d9-f123274646b3), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(425b625d-99ab-408c-86d2-681c4564080e), effect: Forbid, actions: [ViewEntity], resource: Some(Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })) }]" +expression: result.condition.transpile_to_string() +--- +(("entity_temporal_metadata_0_0_0"."entity_uuid" = $1)) AND (NOT((array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $2) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $3)))) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap new file mode 100644 index 00000000000..8e938a3ea94 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })" +expression: expr.transpile_to_string() +--- +array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap new file mode 100644 index 00000000000..52c4c51c18f --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })" +expression: expr.transpile_to_string() +--- +"entity_temporal_metadata_0_0_0"."entity_uuid" = $1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap new file mode 100644 index 00000000000..1f1052e0241 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }" +expression: expr.transpile_to_string() +--- +"entity_temporal_metadata_0_0_0"."web_id" = $1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap new file mode 100644 index 00000000000..8043610bdb3 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "Entity(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))), filter: CreatedByPrincipal })" +expression: expr.transpile_to_string() +--- +("entity_temporal_metadata_0_0_0"."web_id" = $1) AND ("entity_ids_0_0_1"."provenance"->>'createdById' = ($2::text)) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap new file mode 100644 index 00000000000..041d8510acc --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "CreatedByPrincipal, actor = None" +expression: expr.transpile_to_string() +--- +"entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap new file mode 100644 index 00000000000..c187a8e514d --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "CreatedByPrincipal, actor = Some(User(UserId(ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)))))" +expression: expr.transpile_to_string() +--- +"entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap new file mode 100644 index 00000000000..52904e53a4f --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "All { filters: [CreatedByPrincipal, IsOfBaseType { entity_type: \"https://hash.ai/@h/types/entity-type/user/\" }] }" +expression: expr.transpile_to_string() +--- +("entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text)) AND ($2 = ANY("entity_is_of_type_ids_0_0_2"."base_urls")) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap new file mode 100644 index 00000000000..8aab46f3bdd --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "Any { filters: [IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } }, IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/machine/\", version: OntologyTypeVersion { major: 2, pre_release: None } } }] }" +expression: expr.transpile_to_string() +--- +((array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2)) OR (array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $3) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $4))) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap new file mode 100644 index 00000000000..80a84a72960 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "Not { filter: CreatedByPrincipal }" +expression: expr.transpile_to_string() +--- +NOT("entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text)) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap new file mode 100644 index 00000000000..3f5b8f726a6 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "\"https://hash.ai/@h/types/entity-type/machine/\"" +expression: expr.transpile_to_string() +--- +$1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls") diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap new file mode 100644 index 00000000000..faf6470210f --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/machine/\", version: OntologyTypeVersion { major: 1, pre_release: None } }" +expression: expr.transpile_to_string() +--- +array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap new file mode 100644 index 00000000000..a9ca4f70a92 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "OptimizationData { permitted_entity_uuids: [EntityUuid(11111111-1111-1111-1111-111111111111), EntityUuid(22222222-2222-2222-2222-222222222222)], permitted_entity_type_uuids: [], permitted_property_type_uuids: [], permitted_data_type_uuids: [], permitted_web_ids: [] }" +expression: "expr[0].transpile_to_string()" +--- +"entity_temporal_metadata_0_0_0"."entity_uuid" = ANY(($1::uuid[])) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap new file mode 100644 index 00000000000..456f5fa194e --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "OptimizationData { permitted_entity_uuids: [EntityUuid(11111111-1111-1111-1111-111111111111)], permitted_entity_type_uuids: [], permitted_property_type_uuids: [], permitted_data_type_uuids: [], permitted_web_ids: [] }" +expression: "expr[0].transpile_to_string()" +--- +"entity_temporal_metadata_0_0_0"."entity_uuid" = $1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap new file mode 100644 index 00000000000..41162192e00 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "OptimizationData { permitted_entity_uuids: [], permitted_entity_type_uuids: [], permitted_property_type_uuids: [], permitted_data_type_uuids: [], permitted_web_ids: [WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333)))] }" +expression: "expr[0].transpile_to_string()" +--- +"entity_temporal_metadata_0_0_0"."web_id" = $1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_base_only.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_base_only.snap new file mode 100644 index 00000000000..f146840e1d3 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_base_only.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/projections.rs +description: "Projections { index: 1, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: None, entity_ids: None, entity_type_ids: None, left: None, right: None }" +expression: from.transpile_to_string() +--- +"entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_editions_before_continuations.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_editions_before_continuations.snap new file mode 100644 index 00000000000..db43804e947 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_editions_before_continuations.snap @@ -0,0 +1,11 @@ +--- +source: libs/@local/hashql/eval/src/postgres/projections.rs +description: "Projections { index: 2, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }), entity_ids: None, entity_type_ids: None, left: None, right: None }, 1 continuation lateral" +expression: from.transpile_to_string() +--- +"entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +CROSS JOIN LATERAL (SELECT "ee"."entity_edition_id" AS "entity_edition_id", "ee"."properties" AS "properties", "ee"."archived" AS "archived", "ee"."confidence" AS "confidence", "ee"."provenance" AS "provenance", "ee"."property_metadata" AS "property_metadata" +FROM "entity_editions" AS "ee" +WHERE "ee"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_editions_0_0_1" +CROSS JOIN LATERAL (SELECT +FROM "entity_temporal_metadata" AS "continuation_1") AS "continuation_1" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_editions.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_editions.snap new file mode 100644 index 00000000000..a639fd48197 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_editions.snap @@ -0,0 +1,9 @@ +--- +source: libs/@local/hashql/eval/src/postgres/projections.rs +description: "Projections { index: 2, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }), entity_ids: None, entity_type_ids: None, left: None, right: None }" +expression: from.transpile_to_string() +--- +"entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +CROSS JOIN LATERAL (SELECT "ee"."entity_edition_id" AS "entity_edition_id", "ee"."properties" AS "properties", "ee"."archived" AS "archived", "ee"."confidence" AS "confidence", "ee"."provenance" AS "provenance", "ee"."property_metadata" AS "property_metadata" +FROM "entity_editions" AS "ee" +WHERE "ee"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_editions_0_0_1" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_ids_and_editions.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_ids_and_editions.snap new file mode 100644 index 00000000000..99d7138658f --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_from_with_entity_ids_and_editions.snap @@ -0,0 +1,12 @@ +--- +source: libs/@local/hashql/eval/src/postgres/projections.rs +description: "Projections { index: 3, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: Some(Alias { condition_index: 0, chain_depth: 0, number: 2 }), entity_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }), entity_type_ids: None, left: None, right: None }" +expression: from.transpile_to_string() +--- +"entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +INNER JOIN "entity_ids" AS "entity_ids_0_0_1" + ON "entity_ids_0_0_1"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_ids_0_0_1"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" +CROSS JOIN LATERAL (SELECT "ee"."entity_edition_id" AS "entity_edition_id", "ee"."properties" AS "properties", "ee"."archived" AS "archived", "ee"."confidence" AS "confidence", "ee"."provenance" AS "provenance", "ee"."property_metadata" AS "property_metadata" +FROM "entity_editions" AS "ee" +WHERE "ee"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_editions_0_0_2" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap new file mode 100644 index 00000000000..2617daa18b8 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap @@ -0,0 +1,12 @@ +--- +source: libs/@local/hashql/eval/src/postgres/projections.rs +description: "AuxiliaryProjections { index: 2, base: Projections { index: 1, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: None, entity_ids: None, entity_type_ids: None, left: None, right: None }, entity_ids: None, entity_is_of_type_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }) }, 2 continuation laterals" +expression: result.transpile_to_string() +--- +"entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_1" + ON "entity_is_of_type_ids_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" +CROSS JOIN LATERAL (SELECT +FROM "entity_temporal_metadata" AS "continuation_1") AS "continuation_1" +CROSS JOIN LATERAL (SELECT +FROM "entity_temporal_metadata" AS "continuation_2") AS "continuation_2" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap new file mode 100644 index 00000000000..0917dc93750 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap @@ -0,0 +1,11 @@ +--- +source: libs/@local/hashql/eval/src/postgres/projections.rs +description: "AuxiliaryProjections { index: 3, base: Projections { index: 1, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: None, entity_ids: None, entity_type_ids: None, left: None, right: None }, entity_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }), entity_is_of_type_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 2 }) }" +expression: result.transpile_to_string() +--- +"entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +INNER JOIN "entity_ids" AS "entity_ids_0_0_1" + ON "entity_ids_0_0_1"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" + AND "entity_ids_0_0_1"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" +INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" + ON "entity_is_of_type_ids_0_0_2"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap new file mode 100644 index 00000000000..9b3f0b90e62 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "Equal(Path { path: Uuid }, ActorId)" +expression: expr.transpile_to_string() +--- +"entity_temporal_metadata_0_0_0"."entity_uuid" = $1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap new file mode 100644 index 00000000000..0ecd3d87a78 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls })" +expression: expr.transpile_to_string() +--- +$1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls") diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap new file mode 100644 index 00000000000..e091be223be --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "All([In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls }), NotEqual(Path { path: Uuid }, ActorId)])" +expression: expr.transpile_to_string() +--- +($1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap new file mode 100644 index 00000000000..c412bb7c525 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "NotEqual(Path { path: Uuid }, ActorId)" +expression: expr.transpile_to_string() +--- +"entity_temporal_metadata_0_0_0"."entity_uuid" != $1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap new file mode 100644 index 00000000000..f3ff29bf246 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "property: email/, filter: Equal(Path { path: Uuid }, ActorId)" +expression: expr.transpile_to_string() +--- +CASE WHEN "entity_temporal_metadata_0_0_0"."entity_uuid" = $1 THEN ARRAY[$2]::text[] ELSE ARRAY[]::text[] END diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap new file mode 100644 index 00000000000..940f7aad3e8 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "ActorId, actor = Some(User(UserId(ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)))))" +expression: expr.transpile_to_string() +--- +$1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap new file mode 100644 index 00000000000..1652ffb755c --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "Parameter { parameter: Text(\"hello\") }" +expression: expr.transpile_to_string() +--- +$1 diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap new file mode 100644 index 00000000000..963c29c06c4 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: TypeBaseUrls +expression: expr.transpile_to_string() +--- +"entity_is_of_type_ids_0_0_1"."base_urls" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap new file mode 100644 index 00000000000..4f919a38514 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: Uuid +expression: expr.transpile_to_string() +--- +"entity_temporal_metadata_0_0_0"."entity_uuid" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap new file mode 100644 index 00000000000..89d4942b106 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap @@ -0,0 +1,6 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "PropertyProtectionFilterConfig { property_filters: {\"https://hash.ai/@h/types/property-type/email/\": All([In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls }), NotEqual(Path { path: Uuid }, ActorId)])}, embedding_exclusions: {\"https://hash.ai/@h/types/entity-type/user/\": [\"https://hash.ai/@h/types/property-type/email/\"]} }" +expression: expr.transpile_to_string() +--- +(CASE WHEN ($1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) THEN ARRAY[$3]::text[] ELSE ARRAY[]::text[] END) diff --git a/presentation.html b/presentation.html new file mode 100644 index 00000000000..f7321192b45 --- /dev/null +++ b/presentation.html @@ -0,0 +1,413 @@ + + + + + +Authorization in HashQL + + + + + + +
+ + + + + + +
+ +
+
+
Authorization · HASH RFC
+

Authorization
in HashQL

+
A semantics for hiding, from first principles.
+
What it must mean, and why  ·  Reasoning, not implementation
+
+
This is about how HashQL hides data, and why the design looks the way it does. I build it from nothing, one principle at a time, so the semantics feel inevitable. It is about meaning, not code, and I stop before the implementation chapter. Keep one question in mind the entire time: when I deny something, what can an observer still infer from what remains?
+
+ +
+
+
§ 00 · Scope
+

Three permissions, coarse to fine

+
+
Entity
May this actor see this entity at all?
+
Type
May this actor see entities of this type, and the type itself?
+
Cell
May this actor see this property, on this entity, at this path?
+
+
Cell-level is a special case of property permissions. Most of the work is type-level and property-level.
+
+
Three levels, coarse to fine. Entity: can you see this entity at all. Type: can you see entities of a type, and separately the type itself. Cell: property permissions refined to one entity and property. Entity-level is easy and I dispatch it quickly. The other two are hard, because a type already tells a story and a missing field can betray what you tried to hide.
+
+ +
+
+
+
§ 00 · Scope
+

We evaluate the decision.
We do not make it.

+
+
Assumed, not defined

A policy decision matrix

Already computed for one actor and one request, possibly anchored in time. How it was produced, including any temporal reasoning, is out of scope. We define only how HashQL evaluates it over the selected graph.

+
+
A clean boundary. We are not building a policy engine and we do not decide who is allowed what. We assume that decision exists: a matrix that, for this actor and request, says which types, entities, and properties are permitted. Our job begins after the matrix exists. Everything here is the evaluation half.
+
+ +
+
+
§ 00 · The difficulty
+

The crux: referential transparency

+

Once a value is available, it can be transformed, passed through pure functions, stored in other values, or discarded, without changing what the program means.

+
+
load value
+
pure exprfn · aggregate
+
any structure
+
observed
+
+

Evaluating a predicate over a value is already an observation of it.

+
+
The single fact that makes this hard. HashQL is referentially transparent. Once a value is in hand it flows: into arithmetic, a struct, a pure function, or the bin, without changing meaning. So you cannot attach policy to a syntactic position; the value can be anywhere downstream. The sharp version: evaluating a predicate over a value is already an observation of it. A backend that evaluates a filter outside the trusted boundary has just observed the value you were protecting.
+
+ +
+
+
§ 00 · The difficulty
+

Two consequences

+
+
01

Enforce at the evaluation boundary

Co-located with evaluation: backend-side filters and masking at each trusted boundary. Reduced bandwidth is a welcome corollary, never the reason.

+
02

The compiler stays actor-agnostic

Policy is a runtime input. Compilation may annotate its output, never inject actor logic. Visibility: same output for everyone, so it is testable. Caching: the codegen / execution split survives.

+
+
+
Two things follow. Enforcement must sit where evaluation happens; since evaluating is observing, the only safe place is the trusted boundary producing the data. Second, the compiler must not know who is asking. The matrix is a runtime input; compilation may annotate artifacts so decisions become possible later, but never bakes actor logic in. Visibility makes output testable; caching keeps artifacts shared. Remember: annotate, do not inject. It returns as the provenance polynomial.
+
+ +
+
+
§ 00 · The target
+

Never leak hidden information

+

Hold the matrix, snapshot, and action fixed. Nothing may leak through any observable channel:

+
+
Values
the contents themselves
+
Membership
whether a row is in the result
+
Shape
the structure of the result
+
+

And it must stay well-typed. Every value an expression observes must inhabit its declared type, so "just remove the value" is not generally available.

+
+
The target the rest of the talk must hit. Nothing may leak through three channels: values, membership (whether a row is in or out is information), and shape. The decision itself is permitted, you may know you were denied. Side channels like timing are deferred. And one constraint shapes everything: we are strongly typed, so you cannot always just delete a value; the slot still demands something of the right type. That tension drives the masking design.
+
+ +
+
+
01
+
§ 01 · Type-level permissions
+

Types tell
a story

+

Hiding a type is not the same as hiding a value.

+
+
The real subject: type-level permissions. They are hard because a type already tells a story. Knowing an entity's type is information, separate from its values. So denying a type is not like blanking a field. It interacts with inheritance, with multiple types, and with the schema everyone can see. We start simple and add one complication at a time.
+
+ +
+
+
§ 01 · Type-level
+

The isolated case

+ + alice + Person + + is of type + + actor + denied: Person + transitively denied + +

If alice is a Person, and access to Person is denied, the denial applies transitively. No access to the type means no access to the entity.

+
Convention: entities lowercase, types Capitalized.
+
+
Simplest setting: one entity, alice, of one type, Person, no inheritance. Deny Person. The rule is transitive: if I cannot see the type, I cannot see alice. Viewing an entity of a type presupposes viewing the type. This is trivial, but it raises the organising question: what if I explicitly grant alice while Person is denied?
+
+ +
+
+
§ 01 · Type-level
+

The first fork: total or partial?

+

Can an explicit grant on alice pierce a denial on her type? Two readings of what a denial does:

+
+
A · denial is total

A denial propagates. Any applicable denial wins, and no more specific grant can pierce it.

+
B · denial is partial

A denial stops at its target. A grant on alice keeps her observable; what the denial removes is the type itself and what it contributes.

+
+
In the isolated case the two coincide. They diverge the moment a grant, or a second type, enters.
+
+
The organising question. Entities of type Person are denied, but I grant alice. Does the grant win or the denial? A, total: any applicable denial wins, no grant pierces it. B, partial: a denial stops at its target; a grant keeps alice observable, and what is removed is the type and what it contributes. In the isolated case they agree; they diverge once there is a grant to honour or a second type to fall back on. The rest of the talk lives in that divergence.
+
+ +
+
+
§ 01 · Direct inheritance
+

Under B: the surviving facet

+ + alicesalary, department + Employeefacet · own decls + Person + is of type + masked + + +
+
props_V′(alice) = props_V(alice) \ decl(Person)= {salary, department}
+
Definition · facet

A type closed over its visible ancestors only. Person is hidden, so the facet is exactly decl(Employee). Under A, alice would simply vanish.

+
+
+
Add a single inheritance step: Person declares name, Employee adds salary and department, alice is directly an Employee. Deny Person, allow Employee. Under A the denial runs down to Employee and removes alice, the grant is dead. Under B the denial stops at Person and we recompute: alice keeps Employee, drops Person; her properties keep salary and department, drop name. The masking covers the property, the edge, and the derived membership, so the observer never learns the relationship existed. What remains is the Employee facet: a type closed over its visible ancestors only.
+
+ +
+
+
§ 01 · The driving law
+

The input view of any decision is the set of surviving facts from the preceding inheritance layer.

+
Definition · stratum

One inheritance layer of an entity type: the facts a single step of the hierarchy contributes.

+
A decision over a denied property cannot resurrect it. It is not in the input universe of the next decision.
+
+
The single law driving the whole system. The input view of any decision is the set of surviving facts from the preceding inheritance layer. A pipeline: each layer hands the next only what survived. This is what makes type-level and property-level permissions compose. If name was denied upstream, a later decision works over salary and department only. It cannot bring name back, because name is not in its input universe. Composition by construction.
+
+ +
+
+
§ 01 · Reversed denial
+

B.1 and B.2

+
+
B.1 · severs support

A membership survives only while a visible derivation supports it. Direct types root every derivation, so denying a direct type starves the entity.

+
B.2 · demotes

A membership survives while the fact is true and its target visible. The entity is re-presented as its minimal visible supertypes, as if direct.

+
+
We adopt B.1: a substructure view is a function of visible base facts alone (noninterference). B.2 only guarantees consistency with some ground truth. B.2 survives as a deliberate opt-in.
+
+
Reverse the policy: allow Person, deny the direct Employee. alice is a Person only by derivation, through a denied fact. B.1 severs support: the only derivation ran through Employee, so alice vanishes. B.2 demotes: she is re-presented as a plain Person carrying name. We adopt B.1 for three reasons: entityTypeIds is observable so demotion would rewrite data; a substructure view gives noninterference; and it composes to scoped and per-entity denials. B.1 also admits a Boolean valuation, so the absence of leakage is provable.
+
+ +
+
+
§ 01 · The same denial, two intuitions
+

Crop versus blur

+
+
B.1 · crop

The subject leaves the frame. A substructure of the truth; what is hidden is indistinguishable from what never existed.

+
B.2 · blur

The subject stays, coarser. The membership is recovered. Like any blur: given enough context, it sharpens.

+
+
+
A way to feel the difference. If B.1 crops the photograph, B.2 blurs it. Under B.1 the subject leaves the frame: the view is a substructure of the truth, and hidden looks like never-existed. Under B.2 the subject stays, coarser; values stay hidden, but the membership is recovered, and like any blur, with enough context you sharpen it. B.2 matches intuition, see employees as people, real utility and a real attack surface: if genuine direct Persons are sparse, the anonymity set is homogeneous and the hidden membership is recovered per named entity.
+
+ +
+
+
02
+
§ 02 · Multiple inheritance
+

When parents
diverge

+

A type may inherit from several. Flat subtraction breaks.

+
+
Everything so far assumed a single chain. But a type can have several parents, as long as they agree on shared properties. This is where the naive approach, subtract the denied property from a flat set, breaks, and breaks in a way that leaks. The repair is the central rule of the document.
+
+ +
+
+
§ 02 · The diamond & the rule
+

Deny one parent, recompute the rest

+ + alice + SoleProprietordirect type + Personname, dob + Organizationname, reg. no + is of type + inherits + + + actor + denied: Person + +
flat: props \ decl(Person) ⇒ name removed  (leaks)recompute: ∪ decl(visible types) ⇒ name survives via Organization
+
+
A diamond. Person and Organization both declare name, identically. SoleProprietor inherits both; alice is a SoleProprietor. Deny Person. Flat subtraction removes name, but Organization plainly declares it, so redacting it tells the observer a hidden type contributes it: a leak. The correct answer recomputes the union of declarations over visible types, and name survives via Organization. The rule: denials act on base facts; every derived stratum is recomputed from what remains. props is the image of types under decl, so it preserves unions and owes nothing to subtraction.
+
+ +
+
+
§ 02 · Annotate once, value at runtime
+

Support formulas

+

Annotate every base fact with a variable. A derived fact gets a Boolean support formula: sum = alternative lineages, product = a derivation path.

+
+
+ ann(alice carries name) = SP·Person + SP·Organization + ⇒ SP · Organization   survives the denial of Person +
+
+

The polynomial is actor-agnostic and computed once. An observer is a Boolean valuation ν applied at runtime. Annotate, do not inject, now as algebra.

+
+
We make the gate picture exact, with provenance but the Boolean quotient authorization needs. Annotate every base fact with a variable. A sum is alternative lineages, a product is the factors along one path. The diamond: ann of alice carries name is SP times Person plus SP times Organization. Deny Person, set its variable to zero, and the Organization term keeps name alive. An observer is just a Boolean valuation at runtime. The payoff ties back to the start: the formula is computed once at compile time, actor-agnostic; the valuation is applied per actor. Annotate, do not inject, expressed as algebra.
+
+ +
+
+
§ — One rule
+

Subtract the base facts.
Recompute the rest.

+
+

+   Actor-agnostic compilation; the actor is a late valuation.
+   No leak through values, membership, or shape.
+   Always well-typed: the residual is a nameable facet.
+   Provable: Boolean support, noninterference under B.1. +

+
+
The implementation is a separate chapter.
+
+
The whole talk in one line: subtract the base facts, recompute the rest. Facets, severing, the diamond, multi-type entities, re-declaration, scoped denials, are all that one rule under different shapes. Compilation stays actor-agnostic, the actor enters only as a late Boolean valuation. Nothing leaks through values, membership, or shape. The residual is always a type the schema can name, so evaluation stays well-typed. And the positive core is Datalog, so correctness and noninterference under B.1 are provable. How the solver and masking realise it is a separate chapter. Good place to take questions.
+
+ +
+ +
+
+
HASHQL · AUTHZ
+
← → navigate · N notes · F full01 / 18
+
+
Speaker notes
+ + + + \ No newline at end of file From da85a8aa9c5cc0a28155e06f3d5522660131008f Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:34:50 +0200 Subject: [PATCH 18/34] feat: reinforced tests --- .../eval/src/postgres/authorization/plan.md | 82 ++++ .../postgres/authorization/policy/tests.rs | 46 +- .../authorization/protection/tests.rs | 29 +- .../eval/src/postgres/authorization/tests.rs | 31 +- .../algebra_blank_permit_with_forbids.snap | 2 +- .../algebra_constrained_permits_only.snap | 2 +- .../policy/algebra_permits_and_forbids.snap | 2 +- presentation.html | 413 ------------------ 8 files changed, 167 insertions(+), 440 deletions(-) create mode 100644 libs/@local/hashql/eval/src/postgres/authorization/plan.md delete mode 100644 presentation.html diff --git a/libs/@local/hashql/eval/src/postgres/authorization/plan.md b/libs/@local/hashql/eval/src/postgres/authorization/plan.md new file mode 100644 index 00000000000..7585af516b8 --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/authorization/plan.md @@ -0,0 +1,82 @@ +# Authorization test plan + +Remaining work identified by oracle review. Items are grouped by priority. + +## Done (this session) + +- [x] Rename `lower_filter_nested_all_any` to `lower_filter_nested_all` +- [x] Fix `algebra_blank_forbid_resets_projections` (was tautological; now pre-registers + projections via a first transpile, then verifies blank forbid preserves them) +- [x] Deterministic PolicyIds in `permit`/`forbid` helpers (was `Uuid::new_v4()`) +- [x] MockStore asserts expected actor UUID in `determine_actor` +- [x] MockStore asserts expected actor in `build_principal_context` +- [x] MockStore asserts ViewEntity action in `resolve_policies_for_actor` +- [x] Direct `is_instance_admin()` positive/negative assertions in protection test + +## Next: parameter value assertions + +The snapshot tests verify SQL shape but not parameter values. A test passes even +if the wrong UUID is pushed. Add assertions on `AuxiliaryParameters` contents for: + +- `created_by_principal_with_actor` vs `created_by_principal_anonymous` (actor UUID vs public) +- `constraint_exact_entity` (entity UUID value) +- `constraint_web` (web UUID value) +- `optimize_single_entity_uuid` / `optimize_multiple_entity_uuids` (entity UUIDs) +- `resolve_expression_text_parameter` (text value) +- `resolve_expression_actor_id` (actor UUID value) + +This requires exposing parameter values from `AuxiliaryParameters` for test inspection +(currently opaque `Vec>`). + +## Next: E2E integration test (`AuthorizationPatch::patch_query`) + +Build a minimal compiled query (via `PostgresCompiler` or hand-built `PreparedQuery`), +apply `AuthorizationPatch` through the HList pipeline, and verify: + +1. Policy condition added to WHERE clause +2. Auxiliary joins materialized in FROM (entity_ids, entity_is_of_type_ids) +3. Protection mask grafted into entity_editions LATERAL (properties and property_metadata) +4. Auxiliary parameters populated with correct values +5. Join ordering: auth joins before entity_editions LATERAL before continuations + +This is the test that verifies the full onion works end-to-end. + +## Additional coverage (lower priority) + +### Missing filter combinations + +- `EntityResourceFilter::All { filters: vec![] }` (empty conjunction) +- `EntityResourceFilter::Any { filters: vec![] }` (empty disjunction) +- `PropertyFilter::Any` (currently only `All` is tested in nested form) +- Nested `Any` inside `All` +- `EntityResourceFilter::Not` around a compound filter + +### Missing parameter variants in protection + +- `Parameter::Boolean` +- `Parameter::Decimal` +- `Parameter::Uuid` +- `Parameter::OntologyTypeVersion` +- `Parameter::Timestamp` + +### Missing projection configurations + +- `entity_type_ids` (computed LEFT JOIN LATERAL aggregate with parameters) +- `left_entity` / `right_entity` (LEFT OUTER joins) +- Alias reuse: base already has `entity_ids`, auxiliary should reuse alias +- Alias collision: base with many projections, auxiliary aliases must not collide +- Integration: `build_from` with entity_editions, then `build_joins` with auth joins, + verify auth joins appear before entity_editions LATERAL + +### Multiple protected properties + +- Config with two or more protected properties +- Verify `array_cat` combines all CASE WHEN expressions + +### Fixture improvements + +- Actor divergence: `Fixture.policy()` always uses `ACTOR_UUID` regardless of + `PolicyComponents` actor. Consider tying these together or adding + `policy_unit_for(&PolicyComponents)`. +- Non-empty base projections: test that auxiliary projections work correctly + when the base query already requested some tables. diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs index da84163eb5c..b61da13a2cd 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs @@ -453,26 +453,42 @@ fn algebra_permits_and_forbids() { } #[test] -fn algebra_blank_forbid_resets_projections() { +fn algebra_blank_forbid_preserves_prior_projections() { let mut fixture = Fixture::new(); let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); - let policy = policy_components( + + // First transpile registers entity_ids via CreatedByPrincipal. + let first_policy = policy_components( actor, - vec![ - permit(|| { - Some(ResourceConstraint::Entity(EntityResourceConstraint::Any { - filter: EntityResourceFilter::CreatedByPrincipal, - })) - }), - forbid(|| None), - ], + vec![permit(|| { + Some(ResourceConstraint::Entity(EntityResourceConstraint::Any { + filter: EntityResourceFilter::CreatedByPrincipal, + })) + })], ); - let result = fixture + let first_result = fixture .policy() - .transpile(VertexType::Entity, &policy, Global); - assert_eq!(result.condition.transpile_to_string(), "FALSE"); + .transpile(VertexType::Entity, &first_policy, Global); + assert_ne!( + first_result.condition.transpile_to_string(), + "FALSE", + "first transpile should produce a real condition", + ); assert!( - fixture.projections.entity_ids.is_none(), - "blank forbid should reset auxiliary projections", + fixture.projections.entity_ids.is_some(), + "CreatedByPrincipal should register entity_ids", + ); + let entity_ids_alias = fixture.projections.entity_ids; + + // Second transpile with blank forbid should deny all but preserve + // the entity_ids projection registered by the first call. + let second_policy = policy_components(actor, vec![forbid(|| None)]); + let second_result = fixture + .policy() + .transpile(VertexType::Entity, &second_policy, Global); + assert_eq!(second_result.condition.transpile_to_string(), "FALSE"); + assert_eq!( + fixture.projections.entity_ids, entity_ids_alias, + "blank forbid should preserve pre-existing projections", ); } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs index d5e52cd3b2f..b5580a9685d 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs @@ -142,7 +142,7 @@ fn lower_filter_in() { } #[test] -fn lower_filter_nested_all_any() { +fn lower_filter_nested_all() { let mut fixture = Fixture::new(); let filter = PropertyFilter::All(vec![ PropertyFilter::In( @@ -206,7 +206,18 @@ fn transpile_instance_admin_returns_none() { let actor = Some(type_system::principal::actor::ActorId::User( type_system::principal::actor::UserId::new(ACTOR_UUID), )); - let policy = policy_components_admin(actor, vec![]); + let admin_policy = policy_components_admin(actor, vec![]); + let normal_policy = policy_components(actor, vec![]); + + assert!( + admin_policy.is_instance_admin(), + "admin policy should be instance admin", + ); + assert!( + !normal_policy.is_instance_admin(), + "normal policy should not be instance admin", + ); + let mut config = PropertyProtectionFilterConfig::new(); config.protect_property( base_url("https://hash.ai/@h/types/property-type/email/"), @@ -217,8 +228,18 @@ fn transpile_instance_admin_returns_none() { PropertyFilterExpression::ActorId, ), ); - let result = fixture.protection().transpile(&policy, &config); - assert!(result.keys_to_remove.is_none()); + + let admin_result = fixture.protection().transpile(&admin_policy, &config); + assert!( + admin_result.keys_to_remove.is_none(), + "instance admin should bypass protection", + ); + + let normal_result = fixture.protection().transpile(&normal_policy, &config); + assert!( + normal_result.keys_to_remove.is_some(), + "non-admin should produce a mask", + ); } #[test] diff --git a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs index edc8361e122..c2048c28da4 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs @@ -125,8 +125,13 @@ where fn resolve_policies_for_actor( &self, _: AuthenticatedActor, - _: ResolvePoliciesParams<'_>, + params: ResolvePoliciesParams<'_>, ) -> impl Future, Report>> { + assert!( + params.actions.contains(&ActionName::ViewEntity), + "MockStore expects ViewEntity action", + ); + future::ready(Ok(self.policies.iter().map(|policy| (policy)()).collect())) } @@ -261,16 +266,29 @@ where fn determine_actor( &self, - _: ActorEntityUuid, + actor_entity_uuid: ActorEntityUuid, ) -> impl Future, Report>> { + let expected = self + .actor_id + .map_or_else(ActorEntityUuid::public_actor, ActorEntityUuid::from); + assert_eq!( + actor_entity_uuid, expected, + "MockStore received unexpected actor UUID", + ); + future::ready(Ok(self.actor_id)) } fn build_principal_context( &self, - _: ActorId, + actor_id: ActorId, context_builder: &mut ContextBuilder, ) -> impl Future>> { + assert_eq!( + Some(actor_id), + self.actor_id, + "MockStore received unexpected actor in build_principal_context", + ); if self.is_instance_admin { use type_system::principal::actor_group::{ActorGroup, Team}; @@ -312,11 +330,14 @@ pub(crate) fn policy_components( .expect("mock store should not fail") } +const PERMIT_POLICY_UUID: Uuid = Uuid::from_u128(0xBBBB_BBBB_BBBB_BBBB_BBBB_BBBB_BBBB_BBBB); +const FORBID_POLICY_UUID: Uuid = Uuid::from_u128(0xCCCC_CCCC_CCCC_CCCC_CCCC_CCCC_CCCC_CCCC); + pub(crate) fn permit<'resource>( resource: impl Fn() -> Option + Send + Sync + 'resource, ) -> Box ResolvedPolicy + Send + Sync + 'resource> { Box::new(move || ResolvedPolicy { - original_policy_id: PolicyId::new(Uuid::new_v4()), + original_policy_id: PolicyId::new(PERMIT_POLICY_UUID), effect: Effect::Permit, actions: vec![ActionName::ViewEntity], resource: (resource)(), @@ -327,7 +348,7 @@ pub(crate) fn forbid<'resource>( resource: impl Fn() -> Option + Send + Sync + 'resource, ) -> Box ResolvedPolicy + Send + Sync + 'resource> { Box::new(move || ResolvedPolicy { - original_policy_id: PolicyId::new(Uuid::new_v4()), + original_policy_id: PolicyId::new(FORBID_POLICY_UUID), effect: Effect::Forbid, actions: vec![ActionName::ViewEntity], resource: (resource)(), diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap index 2d766707faa..5e9e66e5855 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap @@ -1,6 +1,6 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs -description: "[ResolvedPolicy { original_policy_id: PolicyId(d0ff6345-c5c5-4576-83c6-6694ffe32332), effect: Permit, actions: [ViewEntity], resource: None }, ResolvedPolicy { original_policy_id: PolicyId(412a7e0d-6bc3-478b-86eb-18e0ff70ef14), effect: Forbid, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" +description: "[ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: None }, ResolvedPolicy { original_policy_id: PolicyId(cccccccc-cccc-cccc-cccc-cccccccccccc), effect: Forbid, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" expression: result.condition.transpile_to_string() --- NOT(("entity_temporal_metadata_0_0_0"."web_id" = $1)) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap index 9ea7271cc59..11b2476ae1b 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap @@ -1,6 +1,6 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs -description: "[ResolvedPolicy { original_policy_id: PolicyId(f35ab935-b770-4319-a81a-4cbd1d67bbd1), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(38ad2813-8eac-48c5-a2bd-af2ede9e9065), effect: Permit, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" +description: "[ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" expression: result.condition.transpile_to_string() --- (("entity_temporal_metadata_0_0_0"."entity_uuid" = $1) OR ("entity_temporal_metadata_0_0_0"."web_id" = $2)) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap index 2fc00726afa..420544b58d7 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap @@ -1,6 +1,6 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs -description: "[ResolvedPolicy { original_policy_id: PolicyId(c9a801c0-c283-423a-89d9-f123274646b3), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(425b625d-99ab-408c-86d2-681c4564080e), effect: Forbid, actions: [ViewEntity], resource: Some(Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })) }]" +description: "[ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(cccccccc-cccc-cccc-cccc-cccccccccccc), effect: Forbid, actions: [ViewEntity], resource: Some(Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })) }]" expression: result.condition.transpile_to_string() --- (("entity_temporal_metadata_0_0_0"."entity_uuid" = $1)) AND (NOT((array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $2) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $3)))) diff --git a/presentation.html b/presentation.html deleted file mode 100644 index f7321192b45..00000000000 --- a/presentation.html +++ /dev/null @@ -1,413 +0,0 @@ - - - - - -Authorization in HashQL - - - - - - -
- - - - - - -
- -
-
-
Authorization · HASH RFC
-

Authorization
in HashQL

-
A semantics for hiding, from first principles.
-
What it must mean, and why  ·  Reasoning, not implementation
-
-
This is about how HashQL hides data, and why the design looks the way it does. I build it from nothing, one principle at a time, so the semantics feel inevitable. It is about meaning, not code, and I stop before the implementation chapter. Keep one question in mind the entire time: when I deny something, what can an observer still infer from what remains?
-
- -
-
-
§ 00 · Scope
-

Three permissions, coarse to fine

-
-
Entity
May this actor see this entity at all?
-
Type
May this actor see entities of this type, and the type itself?
-
Cell
May this actor see this property, on this entity, at this path?
-
-
Cell-level is a special case of property permissions. Most of the work is type-level and property-level.
-
-
Three levels, coarse to fine. Entity: can you see this entity at all. Type: can you see entities of a type, and separately the type itself. Cell: property permissions refined to one entity and property. Entity-level is easy and I dispatch it quickly. The other two are hard, because a type already tells a story and a missing field can betray what you tried to hide.
-
- -
-
-
-
§ 00 · Scope
-

We evaluate the decision.
We do not make it.

-
-
Assumed, not defined

A policy decision matrix

Already computed for one actor and one request, possibly anchored in time. How it was produced, including any temporal reasoning, is out of scope. We define only how HashQL evaluates it over the selected graph.

-
-
A clean boundary. We are not building a policy engine and we do not decide who is allowed what. We assume that decision exists: a matrix that, for this actor and request, says which types, entities, and properties are permitted. Our job begins after the matrix exists. Everything here is the evaluation half.
-
- -
-
-
§ 00 · The difficulty
-

The crux: referential transparency

-

Once a value is available, it can be transformed, passed through pure functions, stored in other values, or discarded, without changing what the program means.

-
-
load value
-
pure exprfn · aggregate
-
any structure
-
observed
-
-

Evaluating a predicate over a value is already an observation of it.

-
-
The single fact that makes this hard. HashQL is referentially transparent. Once a value is in hand it flows: into arithmetic, a struct, a pure function, or the bin, without changing meaning. So you cannot attach policy to a syntactic position; the value can be anywhere downstream. The sharp version: evaluating a predicate over a value is already an observation of it. A backend that evaluates a filter outside the trusted boundary has just observed the value you were protecting.
-
- -
-
-
§ 00 · The difficulty
-

Two consequences

-
-
01

Enforce at the evaluation boundary

Co-located with evaluation: backend-side filters and masking at each trusted boundary. Reduced bandwidth is a welcome corollary, never the reason.

-
02

The compiler stays actor-agnostic

Policy is a runtime input. Compilation may annotate its output, never inject actor logic. Visibility: same output for everyone, so it is testable. Caching: the codegen / execution split survives.

-
-
-
Two things follow. Enforcement must sit where evaluation happens; since evaluating is observing, the only safe place is the trusted boundary producing the data. Second, the compiler must not know who is asking. The matrix is a runtime input; compilation may annotate artifacts so decisions become possible later, but never bakes actor logic in. Visibility makes output testable; caching keeps artifacts shared. Remember: annotate, do not inject. It returns as the provenance polynomial.
-
- -
-
-
§ 00 · The target
-

Never leak hidden information

-

Hold the matrix, snapshot, and action fixed. Nothing may leak through any observable channel:

-
-
Values
the contents themselves
-
Membership
whether a row is in the result
-
Shape
the structure of the result
-
-

And it must stay well-typed. Every value an expression observes must inhabit its declared type, so "just remove the value" is not generally available.

-
-
The target the rest of the talk must hit. Nothing may leak through three channels: values, membership (whether a row is in or out is information), and shape. The decision itself is permitted, you may know you were denied. Side channels like timing are deferred. And one constraint shapes everything: we are strongly typed, so you cannot always just delete a value; the slot still demands something of the right type. That tension drives the masking design.
-
- -
-
-
01
-
§ 01 · Type-level permissions
-

Types tell
a story

-

Hiding a type is not the same as hiding a value.

-
-
The real subject: type-level permissions. They are hard because a type already tells a story. Knowing an entity's type is information, separate from its values. So denying a type is not like blanking a field. It interacts with inheritance, with multiple types, and with the schema everyone can see. We start simple and add one complication at a time.
-
- -
-
-
§ 01 · Type-level
-

The isolated case

- - alice - Person - - is of type - - actor - denied: Person - transitively denied - -

If alice is a Person, and access to Person is denied, the denial applies transitively. No access to the type means no access to the entity.

-
Convention: entities lowercase, types Capitalized.
-
-
Simplest setting: one entity, alice, of one type, Person, no inheritance. Deny Person. The rule is transitive: if I cannot see the type, I cannot see alice. Viewing an entity of a type presupposes viewing the type. This is trivial, but it raises the organising question: what if I explicitly grant alice while Person is denied?
-
- -
-
-
§ 01 · Type-level
-

The first fork: total or partial?

-

Can an explicit grant on alice pierce a denial on her type? Two readings of what a denial does:

-
-
A · denial is total

A denial propagates. Any applicable denial wins, and no more specific grant can pierce it.

-
B · denial is partial

A denial stops at its target. A grant on alice keeps her observable; what the denial removes is the type itself and what it contributes.

-
-
In the isolated case the two coincide. They diverge the moment a grant, or a second type, enters.
-
-
The organising question. Entities of type Person are denied, but I grant alice. Does the grant win or the denial? A, total: any applicable denial wins, no grant pierces it. B, partial: a denial stops at its target; a grant keeps alice observable, and what is removed is the type and what it contributes. In the isolated case they agree; they diverge once there is a grant to honour or a second type to fall back on. The rest of the talk lives in that divergence.
-
- -
-
-
§ 01 · Direct inheritance
-

Under B: the surviving facet

- - alicesalary, department - Employeefacet · own decls - Person - is of type - masked - - -
-
props_V′(alice) = props_V(alice) \ decl(Person)= {salary, department}
-
Definition · facet

A type closed over its visible ancestors only. Person is hidden, so the facet is exactly decl(Employee). Under A, alice would simply vanish.

-
-
-
Add a single inheritance step: Person declares name, Employee adds salary and department, alice is directly an Employee. Deny Person, allow Employee. Under A the denial runs down to Employee and removes alice, the grant is dead. Under B the denial stops at Person and we recompute: alice keeps Employee, drops Person; her properties keep salary and department, drop name. The masking covers the property, the edge, and the derived membership, so the observer never learns the relationship existed. What remains is the Employee facet: a type closed over its visible ancestors only.
-
- -
-
-
§ 01 · The driving law
-

The input view of any decision is the set of surviving facts from the preceding inheritance layer.

-
Definition · stratum

One inheritance layer of an entity type: the facts a single step of the hierarchy contributes.

-
A decision over a denied property cannot resurrect it. It is not in the input universe of the next decision.
-
-
The single law driving the whole system. The input view of any decision is the set of surviving facts from the preceding inheritance layer. A pipeline: each layer hands the next only what survived. This is what makes type-level and property-level permissions compose. If name was denied upstream, a later decision works over salary and department only. It cannot bring name back, because name is not in its input universe. Composition by construction.
-
- -
-
-
§ 01 · Reversed denial
-

B.1 and B.2

-
-
B.1 · severs support

A membership survives only while a visible derivation supports it. Direct types root every derivation, so denying a direct type starves the entity.

-
B.2 · demotes

A membership survives while the fact is true and its target visible. The entity is re-presented as its minimal visible supertypes, as if direct.

-
-
We adopt B.1: a substructure view is a function of visible base facts alone (noninterference). B.2 only guarantees consistency with some ground truth. B.2 survives as a deliberate opt-in.
-
-
Reverse the policy: allow Person, deny the direct Employee. alice is a Person only by derivation, through a denied fact. B.1 severs support: the only derivation ran through Employee, so alice vanishes. B.2 demotes: she is re-presented as a plain Person carrying name. We adopt B.1 for three reasons: entityTypeIds is observable so demotion would rewrite data; a substructure view gives noninterference; and it composes to scoped and per-entity denials. B.1 also admits a Boolean valuation, so the absence of leakage is provable.
-
- -
-
-
§ 01 · The same denial, two intuitions
-

Crop versus blur

-
-
B.1 · crop

The subject leaves the frame. A substructure of the truth; what is hidden is indistinguishable from what never existed.

-
B.2 · blur

The subject stays, coarser. The membership is recovered. Like any blur: given enough context, it sharpens.

-
-
-
A way to feel the difference. If B.1 crops the photograph, B.2 blurs it. Under B.1 the subject leaves the frame: the view is a substructure of the truth, and hidden looks like never-existed. Under B.2 the subject stays, coarser; values stay hidden, but the membership is recovered, and like any blur, with enough context you sharpen it. B.2 matches intuition, see employees as people, real utility and a real attack surface: if genuine direct Persons are sparse, the anonymity set is homogeneous and the hidden membership is recovered per named entity.
-
- -
-
-
02
-
§ 02 · Multiple inheritance
-

When parents
diverge

-

A type may inherit from several. Flat subtraction breaks.

-
-
Everything so far assumed a single chain. But a type can have several parents, as long as they agree on shared properties. This is where the naive approach, subtract the denied property from a flat set, breaks, and breaks in a way that leaks. The repair is the central rule of the document.
-
- -
-
-
§ 02 · The diamond & the rule
-

Deny one parent, recompute the rest

- - alice - SoleProprietordirect type - Personname, dob - Organizationname, reg. no - is of type - inherits - - - actor - denied: Person - -
flat: props \ decl(Person) ⇒ name removed  (leaks)recompute: ∪ decl(visible types) ⇒ name survives via Organization
-
-
A diamond. Person and Organization both declare name, identically. SoleProprietor inherits both; alice is a SoleProprietor. Deny Person. Flat subtraction removes name, but Organization plainly declares it, so redacting it tells the observer a hidden type contributes it: a leak. The correct answer recomputes the union of declarations over visible types, and name survives via Organization. The rule: denials act on base facts; every derived stratum is recomputed from what remains. props is the image of types under decl, so it preserves unions and owes nothing to subtraction.
-
- -
-
-
§ 02 · Annotate once, value at runtime
-

Support formulas

-

Annotate every base fact with a variable. A derived fact gets a Boolean support formula: sum = alternative lineages, product = a derivation path.

-
-
- ann(alice carries name) = SP·Person + SP·Organization - ⇒ SP · Organization   survives the denial of Person -
-
-

The polynomial is actor-agnostic and computed once. An observer is a Boolean valuation ν applied at runtime. Annotate, do not inject, now as algebra.

-
-
We make the gate picture exact, with provenance but the Boolean quotient authorization needs. Annotate every base fact with a variable. A sum is alternative lineages, a product is the factors along one path. The diamond: ann of alice carries name is SP times Person plus SP times Organization. Deny Person, set its variable to zero, and the Organization term keeps name alive. An observer is just a Boolean valuation at runtime. The payoff ties back to the start: the formula is computed once at compile time, actor-agnostic; the valuation is applied per actor. Annotate, do not inject, expressed as algebra.
-
- -
-
-
§ — One rule
-

Subtract the base facts.
Recompute the rest.

-
-

-   Actor-agnostic compilation; the actor is a late valuation.
-   No leak through values, membership, or shape.
-   Always well-typed: the residual is a nameable facet.
-   Provable: Boolean support, noninterference under B.1. -

-
-
The implementation is a separate chapter.
-
-
The whole talk in one line: subtract the base facts, recompute the rest. Facets, severing, the diamond, multi-type entities, re-declaration, scoped denials, are all that one rule under different shapes. Compilation stays actor-agnostic, the actor enters only as a late Boolean valuation. Nothing leaks through values, membership, or shape. The residual is always a type the schema can name, so evaluation stays well-typed. And the positive core is Datalog, so correctness and noninterference under B.1 are provable. How the solver and masking realise it is a separate chapter. Good place to take questions.
-
- -
- -
-
-
HASHQL · AUTHZ
-
← → navigate · N notes · F full01 / 18
-
-
Speaker notes
- - - - \ No newline at end of file From 20c6ca4ed51516d64c3a77859dfa2dbccf8ad161 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:03:44 +0200 Subject: [PATCH 19/34] feat: integration tests --- .../eval/src/postgres/authorization/mod.rs | 10 + .../eval/src/postgres/authorization/plan.md | 82 ----- .../postgres/authorization/policy/tests.rs | 92 ++++-- .../authorization/protection/tests.rs | 58 +++- .../eval/src/postgres/authorization/tests.rs | 285 +++++++++++++++++- .../hashql/eval/src/postgres/filter/tests.rs | 184 +++-------- libs/@local/hashql/eval/src/postgres/mod.rs | 2 + .../hashql/eval/src/postgres/parameters.rs | 1 + .../hashql/eval/src/postgres/prepared.rs | 2 +- libs/@local/hashql/eval/src/postgres/tests.rs | 117 +++++++ .../patch_blank_forbid_denies_all.snap | 97 ++++++ .../patch_blank_permit_no_protection.snap | 41 +++ ...ch_instance_admin_bypasses_protection.snap | 67 ++++ .../patch_with_policy_and_protection.snap | 104 +++++++ .../algebra_blank_permit_with_forbids.snap | 4 +- .../algebra_constrained_permits_only.snap | 4 +- .../policy/algebra_permits_and_forbids.snap | 4 +- .../constraint_any_with_type_filter.snap | 4 +- .../policy/constraint_exact_entity.snap | 4 +- .../authorization/policy/constraint_web.snap | 4 +- .../constraint_web_with_created_by.snap | 4 +- .../created_by_principal_anonymous.snap | 4 +- .../created_by_principal_with_actor.snap | 4 +- .../policy/filter_all_conjunction.snap | 4 +- .../policy/filter_any_disjunction.snap | 4 +- .../policy/filter_not_negation.snap | 4 +- .../policy/is_of_base_type_any.snap | 4 +- .../policy/is_of_type_overlap.snap | 4 +- .../policy/optimize_multiple_entities.snap | 4 +- .../policy/optimize_single_entity.snap | 4 +- .../policy/optimize_single_web.snap | 4 +- .../protection/lower_filter_equal.snap | 4 +- .../protection/lower_filter_in.snap | 4 +- .../protection/lower_filter_nested_all.snap | 4 +- .../protection/lower_filter_not_equal.snap | 4 +- .../lower_property_filter_case_when.snap | 4 +- .../resolve_expression_actor_id.snap | 4 +- .../protection/resolve_expression_text.snap | 4 +- .../resolve_path_type_base_urls.snap | 4 +- .../protection/resolve_path_uuid.snap | 4 +- .../protection/transpile_hash_default.snap | 4 +- .../data_island_provides_without_lateral.snap | 17 +- .../provides_drives_select_and_joins.snap | 17 +- 43 files changed, 980 insertions(+), 304 deletions(-) delete mode 100644 libs/@local/hashql/eval/src/postgres/authorization/plan.md create mode 100644 libs/@local/hashql/eval/src/postgres/tests.rs create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index 952117ebf92..6d1164077af 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -100,6 +100,16 @@ pub struct AuthorizationPatch<'policy, 'path> { properties: &'policy PropertyProtectionFilterConfig<'path>, } +impl<'policy, 'path> AuthorizationPatch<'policy, 'path> { + #[must_use] + pub const fn new( + policy: &'policy PolicyComponents, + properties: &'policy PropertyProtectionFilterConfig<'path>, + ) -> Self { + Self { policy, properties } + } +} + impl PatchPreparedQueryLayer for AuthorizationPatch<'_, '_> { diff --git a/libs/@local/hashql/eval/src/postgres/authorization/plan.md b/libs/@local/hashql/eval/src/postgres/authorization/plan.md deleted file mode 100644 index 7585af516b8..00000000000 --- a/libs/@local/hashql/eval/src/postgres/authorization/plan.md +++ /dev/null @@ -1,82 +0,0 @@ -# Authorization test plan - -Remaining work identified by oracle review. Items are grouped by priority. - -## Done (this session) - -- [x] Rename `lower_filter_nested_all_any` to `lower_filter_nested_all` -- [x] Fix `algebra_blank_forbid_resets_projections` (was tautological; now pre-registers - projections via a first transpile, then verifies blank forbid preserves them) -- [x] Deterministic PolicyIds in `permit`/`forbid` helpers (was `Uuid::new_v4()`) -- [x] MockStore asserts expected actor UUID in `determine_actor` -- [x] MockStore asserts expected actor in `build_principal_context` -- [x] MockStore asserts ViewEntity action in `resolve_policies_for_actor` -- [x] Direct `is_instance_admin()` positive/negative assertions in protection test - -## Next: parameter value assertions - -The snapshot tests verify SQL shape but not parameter values. A test passes even -if the wrong UUID is pushed. Add assertions on `AuxiliaryParameters` contents for: - -- `created_by_principal_with_actor` vs `created_by_principal_anonymous` (actor UUID vs public) -- `constraint_exact_entity` (entity UUID value) -- `constraint_web` (web UUID value) -- `optimize_single_entity_uuid` / `optimize_multiple_entity_uuids` (entity UUIDs) -- `resolve_expression_text_parameter` (text value) -- `resolve_expression_actor_id` (actor UUID value) - -This requires exposing parameter values from `AuxiliaryParameters` for test inspection -(currently opaque `Vec>`). - -## Next: E2E integration test (`AuthorizationPatch::patch_query`) - -Build a minimal compiled query (via `PostgresCompiler` or hand-built `PreparedQuery`), -apply `AuthorizationPatch` through the HList pipeline, and verify: - -1. Policy condition added to WHERE clause -2. Auxiliary joins materialized in FROM (entity_ids, entity_is_of_type_ids) -3. Protection mask grafted into entity_editions LATERAL (properties and property_metadata) -4. Auxiliary parameters populated with correct values -5. Join ordering: auth joins before entity_editions LATERAL before continuations - -This is the test that verifies the full onion works end-to-end. - -## Additional coverage (lower priority) - -### Missing filter combinations - -- `EntityResourceFilter::All { filters: vec![] }` (empty conjunction) -- `EntityResourceFilter::Any { filters: vec![] }` (empty disjunction) -- `PropertyFilter::Any` (currently only `All` is tested in nested form) -- Nested `Any` inside `All` -- `EntityResourceFilter::Not` around a compound filter - -### Missing parameter variants in protection - -- `Parameter::Boolean` -- `Parameter::Decimal` -- `Parameter::Uuid` -- `Parameter::OntologyTypeVersion` -- `Parameter::Timestamp` - -### Missing projection configurations - -- `entity_type_ids` (computed LEFT JOIN LATERAL aggregate with parameters) -- `left_entity` / `right_entity` (LEFT OUTER joins) -- Alias reuse: base already has `entity_ids`, auxiliary should reuse alias -- Alias collision: base with many projections, auxiliary aliases must not collide -- Integration: `build_from` with entity_editions, then `build_joins` with auth joins, - verify auth joins appear before entity_editions LATERAL - -### Multiple protected properties - -- Config with two or more protected properties -- Verify `array_cat` combines all CASE WHEN expressions - -### Fixture improvements - -- Actor divergence: `Fixture.policy()` always uses `ACTOR_UUID` regardless of - `PolicyComponents` actor. Consider tying these together or adding - `policy_unit_for(&PolicyComponents)`. -- Non-empty base projections: test that auxiliary projections work correctly - when the base query already requested some tables. diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs index b61da13a2cd..dde260e9b82 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs @@ -24,9 +24,12 @@ use super::{ convert_created_by_principal, convert_entity_resource_filter, convert_is_of_base_type, convert_is_of_type, convert_resource_constraint, optimize, }; -use crate::postgres::authorization::tests::{ - ACTOR_UUID, ENTITY_UUID_1, ENTITY_UUID_2, Fixture, WEB_UUID_1, forbid, make_url, permit, - policy_components, +use crate::postgres::{ + authorization::tests::{ + ACTOR_UUID, ENTITY_UUID_1, ENTITY_UUID_2, Fixture, WEB_UUID_1, forbid, make_url, permit, + policy_components, + }, + parameters::AuxiliaryParameters, }; fn snapshot_settings() -> Settings { @@ -37,6 +40,10 @@ fn snapshot_settings() -> Settings { settings } +fn snapshot_with_params(sql: &str, parameters: &AuxiliaryParameters) -> String { + format!("{sql}\n\nparameters: {parameters:?}") +} + #[test] fn is_of_type_overlap() { let mut fixture = Fixture::new(); @@ -47,7 +54,10 @@ fn is_of_type_overlap() { let mut settings = snapshot_settings(); settings.set_description(description); let _guard = settings.bind_to_scope(); - assert_snapshot!("is_of_type_overlap", expr.transpile_to_string()); + assert_snapshot!( + "is_of_type_overlap", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -61,7 +71,10 @@ fn is_of_base_type_any() { let mut settings = snapshot_settings(); settings.set_description(description); let _guard = settings.bind_to_scope(); - assert_snapshot!("is_of_base_type_any", expr.transpile_to_string()); + assert_snapshot!( + "is_of_base_type_any", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -77,7 +90,7 @@ fn created_by_principal_with_actor() { let _guard = settings.bind_to_scope(); assert_snapshot!( "created_by_principal_with_actor", - expr.transpile_to_string() + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), ); } @@ -92,7 +105,10 @@ fn created_by_principal_anonymous() { fixture.policy_anon().actor_id )); let _guard = settings.bind_to_scope(); - assert_snapshot!("created_by_principal_anonymous", expr.transpile_to_string()); + assert_snapshot!( + "created_by_principal_anonymous", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -106,7 +122,10 @@ fn constraint_exact_entity() { let mut settings = snapshot_settings(); settings.set_description(format!("{constraint:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("constraint_exact_entity", expr.transpile_to_string()); + assert_snapshot!( + "constraint_exact_entity", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -120,7 +139,10 @@ fn constraint_web() { let mut settings = snapshot_settings(); settings.set_description(format!("{constraint:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("constraint_web", expr.transpile_to_string()); + assert_snapshot!( + "constraint_web", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -138,7 +160,7 @@ fn constraint_any_with_type_filter() { let _guard = settings.bind_to_scope(); assert_snapshot!( "constraint_any_with_type_filter", - expr.transpile_to_string() + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), ); } @@ -154,7 +176,10 @@ fn constraint_web_with_created_by() { let mut settings = snapshot_settings(); settings.set_description(format!("{constraint:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("constraint_web_with_created_by", expr.transpile_to_string()); + assert_snapshot!( + "constraint_web_with_created_by", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -184,7 +209,10 @@ fn filter_all_conjunction() { let mut settings = snapshot_settings(); settings.set_description(format!("{filter:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("filter_all_conjunction", expr.transpile_to_string()); + assert_snapshot!( + "filter_all_conjunction", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -205,7 +233,10 @@ fn filter_any_disjunction() { let mut settings = snapshot_settings(); settings.set_description(format!("{filter:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("filter_any_disjunction", expr.transpile_to_string()); + assert_snapshot!( + "filter_any_disjunction", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -219,13 +250,15 @@ fn filter_not_negation() { let mut settings = snapshot_settings(); settings.set_description(format!("{filter:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("filter_not_negation", expr.transpile_to_string()); + assert_snapshot!( + "filter_not_negation", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] fn optimize_single_entity_uuid() { let mut fixture = Fixture::new(); - let mut unit = fixture.policy(); let mut permits = None; let data = OptimizationData { permitted_entity_uuids: vec![EntityUuid::new(ENTITY_UUID_1)], @@ -234,20 +267,22 @@ fn optimize_single_entity_uuid() { permitted_data_type_uuids: vec![], permitted_web_ids: vec![], }; - optimize(&mut unit, &mut permits, &data); + optimize(&mut fixture.policy(), &mut permits, &data); let expr = permits.expect("should have a permit expression"); assert_eq!(expr.len(), 1); let mut settings = snapshot_settings(); settings.set_description(format!("{data:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("optimize_single_entity", expr[0].transpile_to_string()); + assert_snapshot!( + "optimize_single_entity", + snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters), + ); } #[test] fn optimize_multiple_entity_uuids() { let mut fixture = Fixture::new(); - let mut unit = fixture.policy(); let mut permits = None; let data = OptimizationData { permitted_entity_uuids: vec![ @@ -259,20 +294,22 @@ fn optimize_multiple_entity_uuids() { permitted_data_type_uuids: vec![], permitted_web_ids: vec![], }; - optimize(&mut unit, &mut permits, &data); + optimize(&mut fixture.policy(), &mut permits, &data); let expr = permits.expect("should have a permit expression"); assert_eq!(expr.len(), 1); let mut settings = snapshot_settings(); settings.set_description(format!("{data:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("optimize_multiple_entities", expr[0].transpile_to_string()); + assert_snapshot!( + "optimize_multiple_entities", + snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters), + ); } #[test] fn optimize_single_web_id() { let mut fixture = Fixture::new(); - let mut unit = fixture.policy(); let mut permits = None; let data = OptimizationData { permitted_entity_uuids: vec![], @@ -281,14 +318,17 @@ fn optimize_single_web_id() { permitted_data_type_uuids: vec![], permitted_web_ids: vec![WebId::new(WEB_UUID_1)], }; - optimize(&mut unit, &mut permits, &data); + optimize(&mut fixture.policy(), &mut permits, &data); let expr = permits.expect("should have a permit expression"); assert_eq!(expr.len(), 1); let mut settings = snapshot_settings(); settings.set_description(format!("{data:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("optimize_single_web", expr[0].transpile_to_string()); + assert_snapshot!( + "optimize_single_web", + snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -352,7 +392,7 @@ fn algebra_blank_permit_with_forbids() { let _guard = settings.bind_to_scope(); assert_snapshot!( "algebra_blank_permit_with_forbids", - result.condition.transpile_to_string() + snapshot_with_params(&result.condition.transpile_to_string(), &fixture.parameters), ); } @@ -410,7 +450,7 @@ fn algebra_constrained_permits_only() { let _guard = settings.bind_to_scope(); assert_snapshot!( "algebra_constrained_permits_only", - result.condition.transpile_to_string(), + snapshot_with_params(&result.condition.transpile_to_string(), &fixture.parameters), ); } @@ -448,7 +488,7 @@ fn algebra_permits_and_forbids() { let _guard = settings.bind_to_scope(); assert_snapshot!( "algebra_permits_and_forbids", - result.condition.transpile_to_string(), + snapshot_with_params(&result.condition.transpile_to_string(), &fixture.parameters), ); } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs index b5580a9685d..2721f9f5d59 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs @@ -1,4 +1,4 @@ -use alloc::borrow::Cow; +use alloc::{alloc::Global, borrow::Cow}; use std::path::PathBuf; use hash_graph_postgres_store::store::postgres::query::Transpile as _; @@ -13,10 +13,15 @@ use insta::{Settings, assert_snapshot}; use type_system::ontology::BaseUrl; use super::{lower_filter, lower_property_filter, resolve_expression, resolve_path}; -use crate::postgres::authorization::tests::{ - ACTOR_UUID, Fixture, policy_components, policy_components_admin, +use crate::postgres::{ + authorization::tests::{ACTOR_UUID, Fixture, policy_components, policy_components_admin}, + parameters::AuxiliaryParameters, }; +fn snapshot_with_params(sql: &str, parameters: &AuxiliaryParameters) -> String { + format!("{sql}\n\nparameters: {parameters:?}") +} + fn base_url(url: &str) -> BaseUrl { BaseUrl::new(url.to_owned()).expect("valid base URL") } @@ -40,7 +45,10 @@ fn resolve_path_uuid() { let mut settings = snapshot_settings(); settings.set_description(format!("{:?}", PropertyFilterEntityQueryPath::Uuid)); let _guard = settings.bind_to_scope(); - assert_snapshot!("resolve_path_uuid", expr.transpile_to_string()); + assert_snapshot!( + "resolve_path_uuid", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -54,7 +62,10 @@ fn resolve_path_type_base_urls() { let mut settings = snapshot_settings(); settings.set_description(format!("{:?}", PropertyFilterEntityQueryPath::TypeBaseUrls)); let _guard = settings.bind_to_scope(); - assert_snapshot!("resolve_path_type_base_urls", expr.transpile_to_string()); + assert_snapshot!( + "resolve_path_type_base_urls", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -68,7 +79,10 @@ fn resolve_expression_text_parameter() { let mut settings = snapshot_settings(); settings.set_description(format!("{param:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("resolve_expression_text", expr.transpile_to_string()); + assert_snapshot!( + "resolve_expression_text", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -85,7 +99,10 @@ fn resolve_expression_actor_id() { fixture.protection().actor_id )); let _guard = settings.bind_to_scope(); - assert_snapshot!("resolve_expression_actor_id", expr.transpile_to_string()); + assert_snapshot!( + "resolve_expression_actor_id", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -102,7 +119,10 @@ fn lower_filter_equal() { let mut settings = snapshot_settings(); settings.set_description(format!("{filter:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("lower_filter_equal", expr.transpile_to_string()); + assert_snapshot!( + "lower_filter_equal", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -119,7 +139,10 @@ fn lower_filter_not_equal() { let mut settings = snapshot_settings(); settings.set_description(format!("{filter:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("lower_filter_not_equal", expr.transpile_to_string()); + assert_snapshot!( + "lower_filter_not_equal", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -138,7 +161,10 @@ fn lower_filter_in() { let mut settings = snapshot_settings(); settings.set_description(format!("{filter:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("lower_filter_in", expr.transpile_to_string()); + assert_snapshot!( + "lower_filter_in", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -167,7 +193,10 @@ fn lower_filter_nested_all() { let mut settings = snapshot_settings(); settings.set_description(format!("{filter:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("lower_filter_nested_all", expr.transpile_to_string()); + assert_snapshot!( + "lower_filter_nested_all", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } #[test] @@ -187,7 +216,7 @@ fn lower_property_filter_case_when() { let _guard = settings.bind_to_scope(); assert_snapshot!( "lower_property_filter_case_when", - expr.transpile_to_string() + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), ); } @@ -256,5 +285,8 @@ fn transpile_hash_default_config() { let mut settings = snapshot_settings(); settings.set_description(format!("{config:?}")); let _guard = settings.bind_to_scope(); - assert_snapshot!("transpile_hash_default", expr.transpile_to_string()); + assert_snapshot!( + "transpile_hash_default", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs index c2048c28da4..8059f8b2dbe 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs @@ -3,8 +3,8 @@ //! Returns pre-configured policies without requiring a database connection. use alloc::alloc::Global; -use core::future; -use std::collections::HashSet; +use core::{fmt::Write as _, future}; +use std::{collections::HashSet, path::PathBuf}; use error_stack::Report; use hash_graph_authorization::policies::{ @@ -28,8 +28,22 @@ use hash_graph_authorization::policies::{ }, }, }; +use hash_graph_store::filter::protection::{ + PropertyFilter, PropertyFilterEntityQueryPath, PropertyFilterExpression, + PropertyProtectionFilterConfig, +}; +use hashql_core::{ + heap::Heap, module::std_lib::graph::types::knowledge::entity as entity_types, symbol::sym, + r#type::environment::Environment, +}; +use hashql_mir::{ + body::{basic_block::BasicBlockId, local::Local, terminator::GraphReadBody}, + builder::body, + intern::Interner, +}; +use insta::{Settings, assert_snapshot}; use type_system::{ - knowledge::entity::id::EntityEditionId, + knowledge::entity::id::{EntityEditionId, EntityUuid}, ontology::{BaseUrl, VersionedUrl, id::OntologyTypeVersion}, principal::{ actor::{ActorEntityUuid, ActorId, MachineId, UserId}, @@ -40,10 +54,14 @@ use type_system::{ use uuid::Uuid; use super::{policy::PolicyTranslationUnit, protection::ProtectionTranslationUnit}; -use crate::postgres::{ - Parameters, - parameters::AuxiliaryParameters, - projections::{AuxiliaryProjections, Projections}, +use crate::{ + context::CodeGenerationContext, + postgres::{ + AuthorizationPatch, Parameters, PostgresCompiler, PreparedQueryPatch, + parameters::AuxiliaryParameters, + projections::{AuxiliaryProjections, Projections}, + tests::{CompilationFixture, format_body, lint_sql}, + }, }; pub(crate) struct Fixture { @@ -385,3 +403,256 @@ pub(crate) fn make_url(base: &str, version: u32) -> VersionedUrl { }, } } + +fn compile_and_patch<'heap>( + fixture: &CompilationFixture<'heap>, + heap: &'heap Heap, + policy: &hash_graph_authorization::policies::PolicyComponents, + properties: &PropertyProtectionFilterConfig<'_>, +) -> String { + let mut scratch = hashql_core::heap::Scratch::new(); + let def = fixture.def(); + + let mut context = CodeGenerationContext::new_in( + &fixture.env, + &fixture.interner, + &fixture.bodies, + &fixture.execution, + heap, + &mut scratch, + ); + + let mut filters = hashql_core::heap::Vec::new_in(heap); + filters.push(GraphReadBody::Filter(def, Local::ENV)); + + let read = hashql_mir::body::terminator::GraphRead { + head: hashql_mir::body::terminator::GraphReadHead::Entity { + axis: hashql_mir::body::operand::Operand::Place(hashql_mir::body::place::Place::local( + Local::ENV, + )), + }, + body: filters, + tail: hashql_mir::body::terminator::GraphReadTail::Collect, + target: BasicBlockId::START, + }; + + let mut prepared_query = { + let mut compiler = PostgresCompiler::new_in(&mut context, &mut scratch); + compiler.compile_graph_read(&read) + }; + + assert!( + context.diagnostics.is_empty(), + "unexpected diagnostics from compilation", + ); + + let mut patch = PreparedQueryPatch::new().layer(AuthorizationPatch::new(policy, properties)); + patch.apply(&mut prepared_query, Global); + + let body = format_body(fixture, heap); + let sql = lint_sql(&prepared_query.transpile().to_string()); + let compiled_params = format!("{}", prepared_query.parameters); + let auxiliary_params = format!("{:?}", prepared_query.auxiliary_parameters); + + let mut output = String::new(); + writeln!(output, "MIR:").expect("write to String"); + write!(output, "{body}").expect("write to String"); + writeln!(output, "\nSQL:").expect("write to String"); + write!(output, "{sql}").expect("write to String"); + if !compiled_params.is_empty() { + writeln!(output, "\nCompiled parameters:").expect("write to String"); + write!(output, "{compiled_params}").expect("write to String"); + } + writeln!(output, "\nAuxiliary parameters:").expect("write to String"); + write!(output, "{auxiliary_params}").expect("write to String"); + output +} + +fn snapshot_settings() -> Settings { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut settings = Settings::clone_current(); + settings.set_snapshot_path(manifest_dir.join("tests/ui/postgres/authorization/integration")); + settings.set_prepend_module_to_snapshot(false); + settings +} + +/// Compiles a property-accessing filter, then applies authorization with +/// constrained permits, forbids, and property protection masking. +#[test] +fn patch_with_policy_and_protection() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool { + decl env: (), vertex: (|r#type| entity_types::types::entity(r#type, r#type.unknown(), None)), + field_val: ?, input_val: ?, result: Bool; + @proj v_props = vertex.properties: ?, + v_name = v_props.name: ?; + + bb0() { + field_val = load v_name; + input_val = input.load! "expected"; + result = bin.== field_val input_val; + return result; + } + }); + + let compilation = CompilationFixture::new(&heap, env, body); + + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components( + actor, + vec![ + permit(|| { + Some( + hash_graph_authorization::policies::resource::ResourceConstraint::Entity( + hash_graph_authorization::policies::resource::EntityResourceConstraint::Exact { + id: EntityUuid::new(ENTITY_UUID_1), + }, + ), + ) + }), + permit(|| { + Some( + hash_graph_authorization::policies::resource::ResourceConstraint::Web { + web_id: WebId::new(WEB_UUID_1), + }, + ) + }), + forbid(|| { + Some( + hash_graph_authorization::policies::resource::ResourceConstraint::Entity( + hash_graph_authorization::policies::resource::EntityResourceConstraint::Any { + filter: hash_graph_authorization::policies::resource::EntityResourceFilter::IsOfType { + entity_type: make_url( + "https://hash.ai/@h/types/entity-type/restricted/", + 1, + ), + }, + }, + ), + ) + }), + ], + ); + + let mut properties = PropertyProtectionFilterConfig::new(); + properties.protect_property( + BaseUrl::new("https://hash.ai/@h/types/property-type/email/".to_owned()) + .expect("valid base URL"), + PropertyFilter::Equal( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ), + ); + + let report = compile_and_patch(&compilation, &heap, &policy, &properties); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("patch_with_policy_and_protection", report); +} + +/// Blank permit with no protection produces minimal changes: +/// WHERE gets TRUE, no property masking, no auxiliary joins. +#[test] +fn patch_blank_permit_no_protection() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool { + decl env: (), vertex: [Opaque sym::path::Entity; ?], + result: Bool; + + bb0() { + result = input.load! "flag"; + return result; + } + }); + + let compilation = CompilationFixture::new(&heap, env, body); + + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components(actor, vec![permit(|| None)]); + let properties = PropertyProtectionFilterConfig::new(); + + let report = compile_and_patch(&compilation, &heap, &policy, &properties); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("patch_blank_permit_no_protection", report); +} + +/// Blank forbid produces FALSE in WHERE regardless of other policies. +/// Protection masking still applies as defense-in-depth. +#[test] +fn patch_blank_forbid_denies_all() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool { + decl env: (), vertex: (|r#type| entity_types::types::entity(r#type, r#type.unknown(), None)), + field_val: ?, input_val: ?, result: Bool; + @proj v_props = vertex.properties: ?, + v_name = v_props.name: ?; + + bb0() { + field_val = load v_name; + input_val = input.load! "expected"; + result = bin.== field_val input_val; + return result; + } + }); + + let compilation = CompilationFixture::new(&heap, env, body); + + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components(actor, vec![forbid(|| None)]); + let properties = PropertyProtectionFilterConfig::hash_default(); + + let report = compile_and_patch(&compilation, &heap, &policy, &properties); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("patch_blank_forbid_denies_all", report); +} + +/// Instance admin bypasses property protection entirely, even with +/// a non-empty protection config. +#[test] +fn patch_instance_admin_bypasses_protection() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool { + decl env: (), vertex: (|r#type| entity_types::types::entity(r#type, r#type.unknown(), None)), + field_val: ?, input_val: ?, result: Bool; + @proj v_props = vertex.properties: ?, + v_name = v_props.name: ?; + + bb0() { + field_val = load v_name; + input_val = input.load! "expected"; + result = bin.== field_val input_val; + return result; + } + }); + + let compilation = CompilationFixture::new(&heap, env, body); + + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components_admin(actor, vec![permit(|| None)]); + let properties = PropertyProtectionFilterConfig::hash_default(); + + let report = compile_and_patch(&compilation, &heap, &policy, &properties); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("patch_instance_admin_bypasses_protection", report); +} diff --git a/libs/@local/hashql/eval/src/postgres/filter/tests.rs b/libs/@local/hashql/eval/src/postgres/filter/tests.rs index 94dff504fb5..30a76976711 100644 --- a/libs/@local/hashql/eval/src/postgres/filter/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/filter/tests.rs @@ -16,91 +16,28 @@ use hashql_core::{ heap::{Heap, Scratch}, id::Id as _, module::std_lib::graph::types::knowledge::entity as entity_types, - pretty::Formatter, symbol::sym, - r#type::{TypeBuilder, TypeFormatter, TypeFormatterOptions, TypeId, environment::Environment}, + r#type::{TypeBuilder, TypeId, environment::Environment}, }; -use hashql_diagnostics::DiagnosticIssues; use hashql_hir::node::operation::InputOp; use hashql_mir::{ body::{Body, Source, basic_block::BasicBlockId, local::Local, terminator::GraphReadBody}, builder::{BodyBuilder, body}, - context::MirContext, - def::{DefId, DefIdVec}, + def::DefId, intern::Interner, - pass::{ - GlobalAnalysisPass as _, - analysis::SizeEstimationAnalysis, - execution::{ExecutionAnalysis, ExecutionAnalysisResidual, IslandKind, TargetId}, - }, - pretty::TextFormatOptions, + pass::execution::{IslandKind, TargetId}, }; use insta::{Settings, assert_snapshot}; -use sqruff_lib::core::{config::FluffConfig, linter::core::Linter}; -use sqruff_lib_core::dialects::init::DialectKind; use crate::{ context::CodeGenerationContext, - postgres::{DatabaseContext, PostgresCompiler, filter::GraphReadFilterCompiler}, + postgres::{ + DatabaseContext, PostgresCompiler, + filter::GraphReadFilterCompiler, + tests::{CompilationFixture, format_body, lint_sql}, + }, }; -/// Runs the full execution analysis pipeline on a single `body!`-constructed filter body -/// and returns everything needed for compilation. -struct Fixture<'heap> { - env: Environment<'heap>, - interner: crate::intern::Interner<'heap>, - bodies: DefIdVec, &'heap Heap>, - execution: DefIdVec>, &'heap Heap>, -} - -impl<'heap> Fixture<'heap> { - fn new(heap: &'heap Heap, env: Environment<'heap>, body: Body<'heap>) -> Self { - assert!( - matches!(body.source, Source::GraphReadFilter(_)), - "these tests require GraphReadFilter bodies", - ); - - let interner = Interner::new(heap); - let mut scratch = Scratch::new(); - - let mut bodies = DefIdVec::new_in(heap); - bodies.push(body); - - let mut mir_context = MirContext { - heap, - env: &env, - interner: &interner, - diagnostics: DiagnosticIssues::new(), - }; - - let mut size_analysis = SizeEstimationAnalysis::new_in(&scratch); - size_analysis.run(&mut mir_context, &bodies); - let footprints = size_analysis.finish(); - - let analysis = ExecutionAnalysis { - footprints: &footprints, - scratch: &mut scratch, - }; - let execution = analysis.run_all_in(&mut mir_context, &mut bodies, heap); - - assert!( - mir_context.diagnostics.is_empty(), - "execution analysis produced diagnostics: this likely means the body is malformed", - ); - - Self { - env, - interner: interner.into(), - bodies, - execution, - } - } - - fn def(&self) -> DefId { - self.bodies.iter().next().expect("fixture has one body").id - } -} - struct FilterIslandReport { entry_block: BasicBlockId, target: TargetId, @@ -130,27 +67,10 @@ impl core::fmt::Display for FilterReport { } } -fn format_body<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> String { - let formatter = Formatter::new(heap); - let mut type_formatter = - TypeFormatter::new(&formatter, &fixture.env, TypeFormatterOptions::terse()); - - let mut text_format = TextFormatOptions { - writer: Vec::::new(), - indent: 4, - sources: (), - types: &mut type_formatter, - annotations: (), - } - .build(); - - let body = &fixture.bodies[fixture.def()]; - text_format.format_body(body).expect("formatting failed"); - - String::from_utf8(text_format.writer).expect("valid UTF-8") -} - -fn compile_filter_islands<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> FilterReport { +fn compile_filter_islands<'heap>( + fixture: &CompilationFixture<'heap>, + heap: &'heap Heap, +) -> FilterReport { let mut scratch = Scratch::new(); let def = fixture.def(); @@ -182,12 +102,6 @@ fn compile_filter_islands<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> let mut island_reports = Vec::new(); - let mut linter_config = FluffConfig::default(); - linter_config - .override_dialect(DialectKind::Postgres) - .expect("dialect should be loaded"); - let linter = Linter::new(linter_config, None, None, false).expect("linter should be created"); - #[expect(clippy::string_slice, reason = "Known to be valid codepoints")] for (island_id, entry_block) in postgres_islands { let island = &residual.islands[island_id]; @@ -205,11 +119,9 @@ fn compile_filter_islands<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> let sql = expression.transpile_to_string(); - let linted = linter - .lint_string(&format!("SELECT {sql}"), None, true) - .expect("should be valid SQL"); - - let fixed = linted.fix_string(); + // Wrap in SELECT so sqruff can parse the expression, then strip the + // prefix and re-indent. + let fixed = lint_sql(&format!("SELECT {sql}")); let fixed: String = fixed[7..] .lines() .map(|line| &line[4..]) @@ -268,7 +180,10 @@ impl core::fmt::Display for QueryReport { } } -fn compile_full_query<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> QueryReport { +fn compile_full_query<'heap>( + fixture: &CompilationFixture<'heap>, + heap: &'heap Heap, +) -> QueryReport { let mut scratch = Scratch::new(); let def = fixture.def(); @@ -305,22 +220,11 @@ fn compile_full_query<'heap>(fixture: &Fixture<'heap>, heap: &'heap Heap) -> Que "unexpected diagnostics from full compilation", ); - let mut linter_config = FluffConfig::default(); - linter_config - .override_dialect(DialectKind::Postgres) - .expect("dialect should be loaded"); - let linter = Linter::new(linter_config, None, None, false).expect("linter should be created"); - - let sql = prepared_query.transpile().to_string(); - let linted = linter - .lint_string(&sql, None, true) - .expect("should be valid SQL"); - let parameters = format!("{}", prepared_query.parameters); QueryReport { body: format_body(fixture, heap), - sql: linted.fix_string(), + sql: lint_sql(&prepared_query.transpile().to_string()), parameters, } } @@ -360,7 +264,7 @@ fn diamond_cfg_merge() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -390,7 +294,7 @@ fn switch_int_many_branches() { bb5() { return true; } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -422,7 +326,7 @@ fn straight_line_goto_chain() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -467,7 +371,7 @@ fn island_exit_goto() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -509,7 +413,7 @@ fn island_exit_with_live_out() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -556,7 +460,7 @@ fn island_exit_switch_int() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -598,7 +502,7 @@ fn island_exit_empty_arrays() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -631,7 +535,7 @@ fn data_island_provides_without_lateral() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); // No Postgres exec islands should exist — only a Data island. let filter_report = compile_filter_islands(&fixture, &heap); @@ -684,7 +588,7 @@ fn provides_drives_select_and_joins() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_full_query(&fixture, &heap); let settings = snapshot_settings(); @@ -714,7 +618,7 @@ fn property_field_equality() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -745,7 +649,7 @@ fn nested_property_access() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -776,7 +680,7 @@ fn left_entity_filter() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -804,7 +708,7 @@ fn field_index_projection() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -832,7 +736,7 @@ fn field_by_name_projection() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -884,7 +788,7 @@ fn dynamic_index_projection() { body.source = Source::GraphReadFilter(hashql_hir::node::HirId::PLACEHOLDER); body.id = DefId::new(0); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -910,7 +814,7 @@ fn unary_neg() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -936,7 +840,7 @@ fn unary_not() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -963,7 +867,7 @@ fn binary_sub_numeric_cast() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -989,7 +893,7 @@ fn unary_bitnot() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -1026,7 +930,7 @@ fn temporal_decision_time_interval() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_full_query(&fixture, &heap); let settings = snapshot_settings(); @@ -1053,7 +957,7 @@ fn binary_bitand_bigint_cast() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -1080,7 +984,7 @@ fn binary_bitand_boolean_and() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -1107,7 +1011,7 @@ fn binary_bitor_bigint_cast() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); @@ -1134,7 +1038,7 @@ fn binary_bitor_boolean_or() { } }); - let fixture = Fixture::new(&heap, env, body); + let fixture = CompilationFixture::new(&heap, env, body); let report = compile_filter_islands(&fixture, &heap); let settings = snapshot_settings(); diff --git a/libs/@local/hashql/eval/src/postgres/mod.rs b/libs/@local/hashql/eval/src/postgres/mod.rs index c888418ade8..95ab001275a 100644 --- a/libs/@local/hashql/eval/src/postgres/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/mod.rs @@ -77,6 +77,8 @@ mod filter; mod parameters; mod prepared; mod projections; +#[cfg(test)] +pub(crate) mod tests; mod traverse; mod types; diff --git a/libs/@local/hashql/eval/src/postgres/parameters.rs b/libs/@local/hashql/eval/src/postgres/parameters.rs index 061c57a6c0c..38c9c10e121 100644 --- a/libs/@local/hashql/eval/src/postgres/parameters.rs +++ b/libs/@local/hashql/eval/src/postgres/parameters.rs @@ -252,6 +252,7 @@ impl fmt::Display for Parameters<'_, A> { /// Runtime parameter values for authorization conditions, indexed after /// the compiled parameters (`$K+1..`). +#[derive(Debug)] pub(crate) struct AuxiliaryParameters { initial_offset: usize, parameters: Vec, A>, diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs index 72a6d9e9de2..5b0139fe8fe 100644 --- a/libs/@local/hashql/eval/src/postgres/prepared.rs +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -44,7 +44,7 @@ pub struct HCons { tail: T, } -struct HNil; +pub struct HNil; pub struct PatchContext<'ctx, A: Allocator> { pub projections: AuxiliaryProjections, diff --git a/libs/@local/hashql/eval/src/postgres/tests.rs b/libs/@local/hashql/eval/src/postgres/tests.rs new file mode 100644 index 00000000000..63547197cfb --- /dev/null +++ b/libs/@local/hashql/eval/src/postgres/tests.rs @@ -0,0 +1,117 @@ +//! Shared test infrastructure for Postgres compiler tests. + +use hashql_core::{ + heap::{Heap, Scratch}, + pretty::Formatter, + r#type::{TypeFormatter, TypeFormatterOptions, environment::Environment}, +}; +use hashql_diagnostics::DiagnosticIssues; +use hashql_mir::{ + body::{Body, Source}, + context::MirContext, + def::{DefId, DefIdVec}, + intern::Interner, + pass::{ + GlobalAnalysisPass as _, + analysis::SizeEstimationAnalysis, + execution::{ExecutionAnalysis, ExecutionAnalysisResidual}, + }, + pretty::TextFormatOptions, +}; +use sqruff_lib::core::{config::FluffConfig, linter::core::Linter}; +use sqruff_lib_core::dialects::init::DialectKind; + +/// Compiles a single MIR filter body through execution analysis. +/// +/// Provides the analyzed body, environment, and interner needed by +/// [`PostgresCompiler`](super::PostgresCompiler) and downstream test helpers. +pub(super) struct CompilationFixture<'heap> { + pub env: Environment<'heap>, + pub interner: crate::intern::Interner<'heap>, + pub bodies: DefIdVec, &'heap Heap>, + pub execution: DefIdVec>, &'heap Heap>, +} + +impl<'heap> CompilationFixture<'heap> { + pub(super) fn new(heap: &'heap Heap, env: Environment<'heap>, body: Body<'heap>) -> Self { + assert!( + matches!(body.source, Source::GraphReadFilter(_)), + "these tests require GraphReadFilter bodies", + ); + + let interner = Interner::new(heap); + let mut scratch = Scratch::new(); + + let mut bodies = DefIdVec::new_in(heap); + bodies.push(body); + + let mut mir_context = MirContext { + heap, + env: &env, + interner: &interner, + diagnostics: DiagnosticIssues::new(), + }; + + let mut size_analysis = SizeEstimationAnalysis::new_in(&scratch); + size_analysis.run(&mut mir_context, &bodies); + let footprints = size_analysis.finish(); + + let analysis = ExecutionAnalysis { + footprints: &footprints, + scratch: &mut scratch, + }; + let execution = analysis.run_all_in(&mut mir_context, &mut bodies, heap); + + assert!( + mir_context.diagnostics.is_empty(), + "execution analysis produced diagnostics: this likely means the body is malformed", + ); + + Self { + env, + interner: interner.into(), + bodies, + execution, + } + } + + pub(super) fn def(&self) -> DefId { + self.bodies.iter().next().expect("fixture has one body").id + } +} + +/// Pretty-prints the MIR body from a compilation fixture. +pub(super) fn format_body<'heap>(fixture: &CompilationFixture<'heap>, heap: &'heap Heap) -> String { + let formatter = Formatter::new(heap); + let mut type_formatter = + TypeFormatter::new(&formatter, &fixture.env, TypeFormatterOptions::terse()); + + let mut text_format = TextFormatOptions { + writer: Vec::::new(), + indent: 4, + sources: (), + types: &mut type_formatter, + annotations: (), + } + .build(); + + let body = &fixture.bodies[fixture.def()]; + text_format.format_body(body).expect("formatting failed"); + + String::from_utf8(text_format.writer).expect("valid UTF-8") +} + +/// Lints a SQL string through sqruff with Postgres dialect. +pub(super) fn lint_sql(sql: &str) -> String { + let mut linter_config = FluffConfig::default(); + linter_config + .override_dialect(DialectKind::Postgres) + .expect("dialect should be loaded"); + let linter = Linter::new(linter_config, None, None, false).expect("linter should be created"); + + let linted = linter + .lint_string(sql, None, true) + .expect("should be valid SQL"); + + linted.fix_string() +} diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap new file mode 100644 index 00000000000..c82dd24485a --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap @@ -0,0 +1,97 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs +expression: report +--- +MIR: +fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { + let %2: ? + let %3: ? + let %4: Boolean + + bb0(): { + %2 = %1.properties.name + %3 = input LOAD expected + %4 = %2 == %3 + + return %4 + } +} +SQL: +SELECT + ("continuation_0_0"."row")."block" AS "continuation_0_0_block", + ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", + ("continuation_0_0"."row")."values" AS "continuation_0_0_values" +FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" + ON + "entity_is_of_type_ids_0_0_2"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +CROSS JOIN LATERAL ( + SELECT + "ee"."entity_edition_id" AS "entity_edition_id", + ( + "ee"."properties" + - ( + CASE + WHEN + ($5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls")) + AND ( + "entity_temporal_metadata_0_0_0"."entity_uuid" != $6 + ) + THEN ARRAY[$7]::text [] + ELSE ARRAY[]::text [] + END + ) + ) AS "properties", + "ee"."archived" AS "archived", + "ee"."confidence" AS "confidence", + "ee"."provenance" AS "provenance", + ( + "ee"."property_metadata" + - ( + CASE + WHEN + ($5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls")) + AND ( + "entity_temporal_metadata_0_0_0"."entity_uuid" != $6 + ) + THEN ARRAY[$7]::text [] + ELSE ARRAY[]::text [] + END + ) + ) AS "property_metadata" + FROM "entity_editions" AS "ee" + WHERE + "ee"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_editions_0_0_1" +CROSS JOIN LATERAL + ( + SELECT + ( + ROW( + COALESCE( + ( + (TO_JSONB(JSONB_EXTRACT_PATH("entity_editions_0_0_1"."properties", ((($3::text))::text))) = TO_JSONB(($4::jsonb)))::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation + ) AS "row" + ) AS "continuation_0_0" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + AND ("continuation_0_0"."row")."filter" IS NOT FALSE + AND FALSE + +Compiled parameters: +$1: TemporalAxis(Transaction) +$2: TemporalAxis(Decision) +$3: Symbol(name) +$4: Input(expected) +Auxiliary parameters: +AuxiliaryParameters { initial_offset: 4, parameters: ["https://hash.ai/@h/types/entity-type/user/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap new file mode 100644 index 00000000000..7f6eea95830 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap @@ -0,0 +1,41 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs +expression: report +--- +MIR: +fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { + let %2: Boolean + + bb0(): { + %2 = input LOAD flag + + return %2 + } +} +SQL: +SELECT + ("continuation_0_0"."row")."block" AS "continuation_0_0_block", + ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", + ("continuation_0_0"."row")."values" AS "continuation_0_0_values" +FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +CROSS JOIN LATERAL + ( + SELECT + ( + ROW( + COALESCE(((($3::jsonb))::boolean), FALSE), NULL, NULL, NULL + )::continuation + ) AS "row" + ) AS "continuation_0_0" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + AND ("continuation_0_0"."row")."filter" IS NOT FALSE + AND TRUE + +Compiled parameters: +$1: TemporalAxis(Transaction) +$2: TemporalAxis(Decision) +$3: Input(flag) +Auxiliary parameters: +AuxiliaryParameters { initial_offset: 3, parameters: [] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap new file mode 100644 index 00000000000..bf805d0bcdf --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap @@ -0,0 +1,67 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs +expression: report +--- +MIR: +fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { + let %2: ? + let %3: ? + let %4: Boolean + + bb0(): { + %2 = %1.properties.name + %3 = input LOAD expected + %4 = %2 == %3 + + return %4 + } +} +SQL: +SELECT + ("continuation_0_0"."row")."block" AS "continuation_0_0_block", + ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", + ("continuation_0_0"."row")."values" AS "continuation_0_0_values" +FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +CROSS JOIN LATERAL ( + SELECT + "ee"."entity_edition_id" AS "entity_edition_id", + "ee"."properties" AS "properties", + "ee"."archived" AS "archived", + "ee"."confidence" AS "confidence", + "ee"."provenance" AS "provenance", + "ee"."property_metadata" AS "property_metadata" + FROM "entity_editions" AS "ee" + WHERE + "ee"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_editions_0_0_1" +CROSS JOIN LATERAL + ( + SELECT + ( + ROW( + COALESCE( + ( + (TO_JSONB(JSONB_EXTRACT_PATH("entity_editions_0_0_1"."properties", ((($3::text))::text))) = TO_JSONB(($4::jsonb)))::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation + ) AS "row" + ) AS "continuation_0_0" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + AND ("continuation_0_0"."row")."filter" IS NOT FALSE + AND TRUE + +Compiled parameters: +$1: TemporalAxis(Transaction) +$2: TemporalAxis(Decision) +$3: Symbol(name) +$4: Input(expected) +Auxiliary parameters: +AuxiliaryParameters { initial_offset: 4, parameters: [] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap new file mode 100644 index 00000000000..8db948e9818 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap @@ -0,0 +1,104 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs +expression: report +--- +MIR: +fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { + let %2: ? + let %3: ? + let %4: Boolean + + bb0(): { + %2 = %1.properties.name + %3 = input LOAD expected + %4 = %2 == %3 + + return %4 + } +} +SQL: +SELECT + ("continuation_0_0"."row")."block" AS "continuation_0_0_block", + ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", + ("continuation_0_0"."row")."values" AS "continuation_0_0_values" +FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" + ON + "entity_is_of_type_ids_0_0_2"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +CROSS JOIN LATERAL ( + SELECT + "ee"."entity_edition_id" AS "entity_edition_id", + ( + "ee"."properties" + - ( + CASE + WHEN + "entity_temporal_metadata_0_0_0"."entity_uuid" = $9 + THEN ARRAY[$10]::text [] + ELSE ARRAY[]::text [] + END + ) + ) AS "properties", + "ee"."archived" AS "archived", + "ee"."confidence" AS "confidence", + "ee"."provenance" AS "provenance", + ( + "ee"."property_metadata" + - ( + CASE + WHEN + "entity_temporal_metadata_0_0_0"."entity_uuid" = $9 + THEN ARRAY[$10]::text [] + ELSE ARRAY[]::text [] + END + ) + ) AS "property_metadata" + FROM "entity_editions" AS "ee" + WHERE + "ee"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_editions_0_0_1" +CROSS JOIN LATERAL + ( + SELECT + ( + ROW( + COALESCE( + ( + (TO_JSONB(JSONB_EXTRACT_PATH("entity_editions_0_0_1"."properties", ((($3::text))::text))) = TO_JSONB(($4::jsonb)))::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation + ) AS "row" + ) AS "continuation_0_0" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + AND ("continuation_0_0"."row")."filter" IS NOT FALSE + AND ( + ( + ("entity_temporal_metadata_0_0_0"."entity_uuid" = $5) + OR ("entity_temporal_metadata_0_0_0"."web_id" = $6) + ) + ) + AND ( + NOT ( + ( + ARRAY_POSITIONS("entity_is_of_type_ids_0_0_2"."base_urls", $7) + && ARRAY_POSITIONS("entity_is_of_type_ids_0_0_2"."versions", $8) + ) + ) + ) + +Compiled parameters: +$1: TemporalAxis(Transaction) +$2: TemporalAxis(Decision) +$3: Symbol(name) +$4: Input(expected) +Auxiliary parameters: +AuxiliaryParameters { initial_offset: 4, parameters: [EntityUuid(11111111-1111-1111-1111-111111111111), WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))), "https://hash.ai/@h/types/entity-type/restricted/", OntologyTypeVersion { major: 1, pre_release: None }, ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap index 5e9e66e5855..668e6126858 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_blank_permit_with_forbids.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "[ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: None }, ResolvedPolicy { original_policy_id: PolicyId(cccccccc-cccc-cccc-cccc-cccccccccccc), effect: Forbid, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" -expression: result.condition.transpile_to_string() +expression: "snapshot_with_params(&result.condition.transpile_to_string(),\n&fixture.parameters)" --- NOT(("entity_temporal_metadata_0_0_0"."web_id" = $1)) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333)))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap index 11b2476ae1b..f016bf06555 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_constrained_permits_only.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "[ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: Some(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }) }]" -expression: result.condition.transpile_to_string() +expression: "snapshot_with_params(&result.condition.transpile_to_string(),\n&fixture.parameters)" --- (("entity_temporal_metadata_0_0_0"."entity_uuid" = $1) OR ("entity_temporal_metadata_0_0_0"."web_id" = $2)) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [EntityUuid(11111111-1111-1111-1111-111111111111), WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333)))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap index 420544b58d7..670002b08f9 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "[ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(cccccccc-cccc-cccc-cccc-cccccccccccc), effect: Forbid, actions: [ViewEntity], resource: Some(Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })) }]" -expression: result.condition.transpile_to_string() +expression: "snapshot_with_params(&result.condition.transpile_to_string(),\n&fixture.parameters)" --- (("entity_temporal_metadata_0_0_0"."entity_uuid" = $1)) AND (NOT((array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $2) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $3)))) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [EntityUuid(11111111-1111-1111-1111-111111111111), "https://hash.ai/@h/types/entity-type/user/", OntologyTypeVersion { major: 1, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap index 8e938a3ea94..72ae9dd5d97 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", OntologyTypeVersion { major: 1, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap index 52c4c51c18f..c832b6ae1c2 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_exact_entity.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."entity_uuid" = $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [EntityUuid(11111111-1111-1111-1111-111111111111)] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap index 1f1052e0241..797105acb8b 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))) }" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."web_id" = $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333)))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap index 8043610bdb3..41544a4e983 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_web_with_created_by.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Entity(Web { web_id: WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))), filter: CreatedByPrincipal })" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- ("entity_temporal_metadata_0_0_0"."web_id" = $1) AND ("entity_ids_0_0_1"."provenance"->>'createdById' = ($2::text)) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))), ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap index 041d8510acc..b6fb3fcfbc6 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_anonymous.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "CreatedByPrincipal, actor = None" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(00000000-0000-0000-0000-000000000000))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap index c187a8e514d..350cce15c0a 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/created_by_principal_with_actor.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "CreatedByPrincipal, actor = Some(User(UserId(ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)))))" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap index 52904e53a4f..7cc3ddb7c8a 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "All { filters: [CreatedByPrincipal, IsOfBaseType { entity_type: \"https://hash.ai/@h/types/entity-type/user/\" }] }" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- ("entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text)) AND ($2 = ANY("entity_is_of_type_ids_0_0_2"."base_urls")) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/entity-type/user/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap index 8aab46f3bdd..fd4d977d7b3 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Any { filters: [IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } }, IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/machine/\", version: OntologyTypeVersion { major: 2, pre_release: None } } }] }" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- ((array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2)) OR (array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $3) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $4))) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", OntologyTypeVersion { major: 1, pre_release: None }, "https://hash.ai/@h/types/entity-type/machine/", OntologyTypeVersion { major: 2, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap index 80a84a72960..137a47725c8 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_not_negation.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Not { filter: CreatedByPrincipal }" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- NOT("entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text)) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap index 3f5b8f726a6..0243095a1d1 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "\"https://hash.ai/@h/types/entity-type/machine/\"" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- $1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls") + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/machine/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap index faf6470210f..2a4b5aafb75 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/machine/\", version: OntologyTypeVersion { major: 1, pre_release: None } }" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/machine/", OntologyTypeVersion { major: 1, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap index a9ca4f70a92..d92d941c87f 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_entities.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "OptimizationData { permitted_entity_uuids: [EntityUuid(11111111-1111-1111-1111-111111111111), EntityUuid(22222222-2222-2222-2222-222222222222)], permitted_entity_type_uuids: [], permitted_property_type_uuids: [], permitted_data_type_uuids: [], permitted_web_ids: [] }" -expression: "expr[0].transpile_to_string()" +expression: "snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."entity_uuid" = ANY(($1::uuid[])) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [[EntityUuid(11111111-1111-1111-1111-111111111111), EntityUuid(22222222-2222-2222-2222-222222222222)]] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap index 456f5fa194e..51f5789b8f5 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_entity.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "OptimizationData { permitted_entity_uuids: [EntityUuid(11111111-1111-1111-1111-111111111111)], permitted_entity_type_uuids: [], permitted_property_type_uuids: [], permitted_data_type_uuids: [], permitted_web_ids: [] }" -expression: "expr[0].transpile_to_string()" +expression: "snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."entity_uuid" = $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [EntityUuid(11111111-1111-1111-1111-111111111111)] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap index 41162192e00..a9004160a74 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_single_web.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "OptimizationData { permitted_entity_uuids: [], permitted_entity_type_uuids: [], permitted_property_type_uuids: [], permitted_data_type_uuids: [], permitted_web_ids: [WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333)))] }" -expression: "expr[0].transpile_to_string()" +expression: "snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."web_id" = $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333)))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap index 9b3f0b90e62..9e488176338 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_equal.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "Equal(Path { path: Uuid }, ActorId)" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."entity_uuid" = $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap index 0ecd3d87a78..7dacc792c0f 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls })" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- $1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls") + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap index e091be223be..7948cc8a267 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "All([In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls }), NotEqual(Path { path: Uuid }, ActorId)])" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- ($1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap index c412bb7c525..33afb9e3d1d 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_not_equal.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "NotEqual(Path { path: Uuid }, ActorId)" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."entity_uuid" != $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap index f3ff29bf246..1d19d19bf9f 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_property_filter_case_when.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "property: email/, filter: Equal(Path { path: Uuid }, ActorId)" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- CASE WHEN "entity_temporal_metadata_0_0_0"."entity_uuid" = $1 THEN ARRAY[$2]::text[] ELSE ARRAY[]::text[] END + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap index 940f7aad3e8..1efe1bae591 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_actor_id.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "ActorId, actor = Some(User(UserId(ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)))))" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap index 1652ffb755c..1aba2af6940 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_expression_text.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "Parameter { parameter: Text(\"hello\") }" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- $1 + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["hello"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap index 963c29c06c4..c466108c35e 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: TypeBaseUrls -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_is_of_type_ids_0_0_1"."base_urls" + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap index 4f919a38514..545e38dda58 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_uuid.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: Uuid -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- "entity_temporal_metadata_0_0_0"."entity_uuid" + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap index 89d4942b106..c515bc2280d 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap @@ -1,6 +1,8 @@ --- source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "PropertyProtectionFilterConfig { property_filters: {\"https://hash.ai/@h/types/property-type/email/\": All([In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls }), NotEqual(Path { path: Uuid }, ActorId)])}, embedding_exclusions: {\"https://hash.ai/@h/types/entity-type/user/\": [\"https://hash.ai/@h/types/property-type/email/\"]} }" -expression: expr.transpile_to_string() +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- (CASE WHEN ($1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) THEN ARRAY[$3]::text[] ELSE ARRAY[]::text[] END) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap index 5a1e06110e6..ac9c3672871 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/data_island_provides_without_lateral.snap @@ -97,10 +97,6 @@ SELECT "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" -INNER JOIN "entity_editions" AS "entity_editions_0_0_1" - ON - "entity_editions_0_0_1"."entity_edition_id" - = "entity_temporal_metadata_0_0_0"."entity_edition_id" INNER JOIN "entity_ids" AS "entity_ids_0_0_3" ON "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" @@ -134,6 +130,19 @@ LEFT OUTER JOIN "entity_has_right_entity" AS "entity_has_right_entity_0_0_5" = "entity_temporal_metadata_0_0_0"."web_id" AND "entity_has_right_entity_0_0_5"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" +CROSS JOIN LATERAL ( + SELECT + "ee"."entity_edition_id" AS "entity_edition_id", + "ee"."properties" AS "properties", + "ee"."archived" AS "archived", + "ee"."confidence" AS "confidence", + "ee"."provenance" AS "provenance", + "ee"."property_metadata" AS "property_metadata" + FROM "entity_editions" AS "ee" + WHERE + "ee"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_editions_0_0_1" WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap b/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap index a918721f4c9..47ebe5203b7 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/filter/provides_drives_select_and_joins.snap @@ -99,10 +99,6 @@ SELECT "entity_has_left_entity_0_0_4"."provenance" AS "left_entity_provenance", "entity_has_right_entity_0_0_5"."provenance" AS "right_entity_provenance" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" -INNER JOIN "entity_editions" AS "entity_editions_0_0_1" - ON - "entity_editions_0_0_1"."entity_edition_id" - = "entity_temporal_metadata_0_0_0"."entity_edition_id" INNER JOIN "entity_ids" AS "entity_ids_0_0_3" ON "entity_ids_0_0_3"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" @@ -136,6 +132,19 @@ LEFT OUTER JOIN "entity_has_right_entity" AS "entity_has_right_entity_0_0_5" = "entity_temporal_metadata_0_0_0"."web_id" AND "entity_has_right_entity_0_0_5"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" +CROSS JOIN LATERAL ( + SELECT + "ee"."entity_edition_id" AS "entity_edition_id", + "ee"."properties" AS "properties", + "ee"."archived" AS "archived", + "ee"."confidence" AS "confidence", + "ee"."provenance" AS "provenance", + "ee"."property_metadata" AS "property_metadata" + FROM "entity_editions" AS "ee" + WHERE + "ee"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_editions_0_0_1" WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) From 3180a4acc529d7aac20976c66076cd2bbe7e7cd6 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:28:44 +0200 Subject: [PATCH 20/34] feat: checkpoint --- .../eval/src/postgres/authorization/mod.rs | 17 +++- .../postgres/authorization/policy/tests.rs | 62 ++++++++++++ .../authorization/protection/tests.rs | 80 ++++++++++++++++ .../eval/src/postgres/authorization/tests.rs | 69 ++++++++++++-- .../hashql/eval/src/postgres/parameters.rs | 6 ++ .../patch_blank_forbid_denies_all.snap | 12 ++- .../patch_blank_permit_no_protection.snap | 12 ++- ...ch_instance_admin_bypasses_protection.snap | 12 ++- .../patch_protection_with_type_base_urls.snap | 95 +++++++++++++++++++ .../patch_with_policy_and_protection.snap | 12 ++- .../policy/optimize_multiple_webs.snap | 8 ++ .../lower_filter_any_disjunction.snap | 8 ++ .../transpile_multiple_properties.snap | 8 ++ 13 files changed, 377 insertions(+), 24 deletions(-) create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_webs.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap create mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index 6d1164077af..db2bf2060a5 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -174,6 +174,9 @@ impl PatchPreparedQueryLayer ); }; + #[cfg(debug_assertions)] + let mut grafted_columns = 0_u32; + for column in &mut statement.selects { let SelectExpression::Expression { expression, @@ -191,9 +194,21 @@ impl PatchPreparedQueryLayer let base = mem::replace(expression, Expression::Parameter(0)); // Group the result of the subtraction so that subsequent operators bind to the - // result, and not to one of it's parts. + // result, and not to one of its parts. *expression = Expression::subtract(base, keys_to_remove.clone()).grouped(); + + if cfg!(debug_assertions) { + grafted_columns += 1; + } } } + + if cfg!(debug_assertions) { + debug_assert_eq!( + grafted_columns, 2, + "entity_editions LATERAL must contain both `properties` and `property_metadata` \ + projections for masking; found {grafted_columns}", + ); + } } } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs index dde260e9b82..639f335decb 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs @@ -331,6 +331,30 @@ fn optimize_single_web_id() { ); } +#[test] +fn optimize_multiple_web_ids() { + let mut fixture = Fixture::new(); + let mut permits = None; + let data = OptimizationData { + permitted_entity_uuids: vec![], + permitted_entity_type_uuids: vec![], + permitted_property_type_uuids: vec![], + permitted_data_type_uuids: vec![], + permitted_web_ids: vec![WebId::new(WEB_UUID_1), WebId::new(ENTITY_UUID_2)], + }; + optimize(&mut fixture.policy(), &mut permits, &data); + let expr = permits.expect("should have a permit expression"); + assert_eq!(expr.len(), 1); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{data:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "optimize_multiple_webs", + snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters), + ); +} + #[test] fn optimize_empty_is_noop() { let mut fixture = Fixture::new(); @@ -344,6 +368,22 @@ fn optimize_empty_is_noop() { ); } +#[test] +fn filter_empty_all_is_true() { + let mut fixture = Fixture::new(); + let filter = EntityResourceFilter::All { filters: vec![] }; + let expr = convert_entity_resource_filter(&mut fixture.policy(), &filter); + assert_eq!(expr.transpile_to_string(), "TRUE"); +} + +#[test] +fn filter_empty_any_is_false() { + let mut fixture = Fixture::new(); + let filter = EntityResourceFilter::Any { filters: vec![] }; + let expr = convert_entity_resource_filter(&mut fixture.policy(), &filter); + assert_eq!(expr.transpile_to_string(), "FALSE"); +} + #[test] fn algebra_blank_forbid_denies_all() { let mut fixture = Fixture::new(); @@ -353,6 +393,19 @@ fn algebra_blank_forbid_denies_all() { .policy() .transpile(VertexType::Entity, &policy, Global); assert_eq!(result.condition.transpile_to_string(), "FALSE"); + assert_eq!( + fixture.parameters.len(), + 0, + "blank forbid should not allocate parameters", + ); + assert!( + fixture.projections.entity_ids.is_none(), + "blank forbid should not register entity_ids", + ); + assert!( + fixture.projections.entity_is_of_type_ids.is_none(), + "blank forbid should not register entity_is_of_type_ids", + ); } #[test] @@ -416,6 +469,15 @@ fn algebra_no_permits_denies_all() { "FALSE", "forbids without permits should deny all", ); + assert_eq!( + fixture.parameters.len(), + 0, + "no-permit early return should not allocate forbid parameters", + ); + assert!( + fixture.projections.entity_ids.is_none(), + "no-permit early return should not register joins", + ); } #[test] diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs index 2721f9f5d59..186aa7fdc01 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs @@ -227,6 +227,11 @@ fn transpile_empty_config_returns_none() { let config = PropertyProtectionFilterConfig::new(); let result = fixture.protection().transpile(&policy, &config); assert!(result.keys_to_remove.is_none()); + assert_eq!( + fixture.parameters.len(), + 0, + "empty config should not allocate parameters", + ); } #[test] @@ -263,6 +268,11 @@ fn transpile_instance_admin_returns_none() { admin_result.keys_to_remove.is_none(), "instance admin should bypass protection", ); + assert_eq!( + fixture.parameters.len(), + 0, + "instance admin bypass should not allocate parameters", + ); let normal_result = fixture.protection().transpile(&normal_policy, &config); assert!( @@ -290,3 +300,73 @@ fn transpile_hash_default_config() { snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), ); } + +#[test] +fn lower_filter_any_disjunction() { + let mut fixture = Fixture::new(); + let filter = PropertyFilter::Any(vec![ + PropertyFilter::Equal( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ), + PropertyFilter::In( + PropertyFilterExpression::Parameter { + parameter: Parameter::Text(Cow::Borrowed( + "https://hash.ai/@h/types/entity-type/user/", + )), + }, + PropertyFilterExpressionList::Path { + path: PropertyFilterEntityQueryPath::TypeBaseUrls, + }, + ), + ]); + let expr = lower_filter(&mut fixture.protection(), &filter); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{filter:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "lower_filter_any_disjunction", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); +} + +#[test] +fn transpile_multiple_protected_properties() { + let mut fixture = Fixture::new(); + let actor = Some(type_system::principal::actor::ActorId::User( + type_system::principal::actor::UserId::new(ACTOR_UUID), + )); + let policy = policy_components(actor, vec![]); + let mut config = PropertyProtectionFilterConfig::new(); + config.protect_property( + base_url("https://hash.ai/@h/types/property-type/email/"), + PropertyFilter::Equal( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ), + ); + config.protect_property( + base_url("https://hash.ai/@h/types/property-type/phone/"), + PropertyFilter::NotEqual( + PropertyFilterExpression::Path { + path: PropertyFilterEntityQueryPath::Uuid, + }, + PropertyFilterExpression::ActorId, + ), + ); + let result = fixture.protection().transpile(&policy, &config); + let expr = result.keys_to_remove.expect("should produce a mask"); + + let mut settings = snapshot_settings(); + settings.set_description(format!("{config:?}")); + let _guard = settings.bind_to_scope(); + assert_snapshot!( + "transpile_multiple_properties", + snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + ); +} diff --git a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs index 8059f8b2dbe..873c02a7f97 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs @@ -28,9 +28,12 @@ use hash_graph_authorization::policies::{ }, }, }; -use hash_graph_store::filter::protection::{ - PropertyFilter, PropertyFilterEntityQueryPath, PropertyFilterExpression, - PropertyProtectionFilterConfig, +use hash_graph_store::filter::{ + Parameter, + protection::{ + PropertyFilter, PropertyFilterEntityQueryPath, PropertyFilterExpression, + PropertyFilterExpressionList, PropertyProtectionFilterConfig, + }, }; use hashql_core::{ heap::Heap, module::std_lib::graph::types::knowledge::entity as entity_types, symbol::sym, @@ -455,15 +458,15 @@ fn compile_and_patch<'heap>( let auxiliary_params = format!("{:?}", prepared_query.auxiliary_parameters); let mut output = String::new(); - writeln!(output, "MIR:").expect("write to String"); + writeln!(output, "{:=^80}\n", " MIR ").expect("write to String"); write!(output, "{body}").expect("write to String"); - writeln!(output, "\nSQL:").expect("write to String"); + writeln!(output, "\n{:=^80}\n", " SQL ").expect("write to String"); write!(output, "{sql}").expect("write to String"); if !compiled_params.is_empty() { - writeln!(output, "\nCompiled parameters:").expect("write to String"); + writeln!(output, "\n{:=^80}\n", " Compiled Parameters ").expect("write to String"); write!(output, "{compiled_params}").expect("write to String"); } - writeln!(output, "\nAuxiliary parameters:").expect("write to String"); + writeln!(output, "\n{:=^80}\n", " Auxiliary Parameters ").expect("write to String"); write!(output, "{auxiliary_params}").expect("write to String"); output } @@ -656,3 +659,55 @@ fn patch_instance_admin_bypasses_protection() { let _guard = settings.bind_to_scope(); assert_snapshot!("patch_instance_admin_bypasses_protection", report); } + +/// Protection filter that references `TypeBaseUrls`, requiring the +/// `entity_is_of_type_ids` auxiliary join to be in scope inside the +/// `entity_editions` LATERAL mask expression. +#[test] +fn patch_protection_with_type_base_urls() { + let heap = Heap::new(); + let interner = Interner::new(&heap); + let env = Environment::new(&heap); + + let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool { + decl env: (), vertex: (|r#type| entity_types::types::entity(r#type, r#type.unknown(), None)), + field_val: ?, input_val: ?, result: Bool; + @proj v_props = vertex.properties: ?, + v_name = v_props.name: ?; + + bb0() { + field_val = load v_name; + input_val = input.load! "expected"; + result = bin.== field_val input_val; + return result; + } + }); + + let compilation = CompilationFixture::new(&heap, env, body); + + let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); + let policy = policy_components(actor, vec![permit(|| None)]); + + // Protection uses TypeBaseUrls path, which demands entity_is_of_type_ids join. + let mut properties = PropertyProtectionFilterConfig::new(); + properties.protect_property( + BaseUrl::new("https://hash.ai/@h/types/property-type/email/".to_owned()) + .expect("valid base URL"), + PropertyFilter::In( + PropertyFilterExpression::Parameter { + parameter: Parameter::Text(alloc::borrow::Cow::Borrowed( + "https://hash.ai/@h/types/entity-type/user/", + )), + }, + PropertyFilterExpressionList::Path { + path: PropertyFilterEntityQueryPath::TypeBaseUrls, + }, + ), + ); + + let report = compile_and_patch(&compilation, &heap, &policy, &properties); + + let settings = snapshot_settings(); + let _guard = settings.bind_to_scope(); + assert_snapshot!("patch_protection_with_type_base_urls", report); +} diff --git a/libs/@local/hashql/eval/src/postgres/parameters.rs b/libs/@local/hashql/eval/src/postgres/parameters.rs index 38c9c10e121..1204857e2b3 100644 --- a/libs/@local/hashql/eval/src/postgres/parameters.rs +++ b/libs/@local/hashql/eval/src/postgres/parameters.rs @@ -276,6 +276,12 @@ impl AuxiliaryParameters
{ self.parameters.len() + self.initial_offset } + + /// Returns the number of auxiliary parameters allocated. + #[cfg(test)] + pub(crate) fn len(&self) -> usize { + self.parameters.len() + } } #[cfg(test)] diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap index c82dd24485a..c189dea705c 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap @@ -2,7 +2,8 @@ source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs expression: report --- -MIR: +===================================== MIR ====================================== + fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { let %2: ? let %3: ? @@ -16,7 +17,8 @@ fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { return %4 } } -SQL: +===================================== SQL ====================================== + SELECT ("continuation_0_0"."row")."block" AS "continuation_0_0_block", ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", @@ -88,10 +90,12 @@ WHERE AND ("continuation_0_0"."row")."filter" IS NOT FALSE AND FALSE -Compiled parameters: +============================= Compiled Parameters ============================== + $1: TemporalAxis(Transaction) $2: TemporalAxis(Decision) $3: Symbol(name) $4: Input(expected) -Auxiliary parameters: +============================= Auxiliary Parameters ============================= + AuxiliaryParameters { initial_offset: 4, parameters: ["https://hash.ai/@h/types/entity-type/user/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap index 7f6eea95830..f6c559203a7 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_permit_no_protection.snap @@ -2,7 +2,8 @@ source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs expression: report --- -MIR: +===================================== MIR ====================================== + fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { let %2: Boolean @@ -12,7 +13,8 @@ fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { return %2 } } -SQL: +===================================== SQL ====================================== + SELECT ("continuation_0_0"."row")."block" AS "continuation_0_0_block", ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", @@ -33,9 +35,11 @@ WHERE AND ("continuation_0_0"."row")."filter" IS NOT FALSE AND TRUE -Compiled parameters: +============================= Compiled Parameters ============================== + $1: TemporalAxis(Transaction) $2: TemporalAxis(Decision) $3: Input(flag) -Auxiliary parameters: +============================= Auxiliary Parameters ============================= + AuxiliaryParameters { initial_offset: 3, parameters: [] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap index bf805d0bcdf..6c237b26310 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_instance_admin_bypasses_protection.snap @@ -2,7 +2,8 @@ source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs expression: report --- -MIR: +===================================== MIR ====================================== + fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { let %2: ? let %3: ? @@ -16,7 +17,8 @@ fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { return %4 } } -SQL: +===================================== SQL ====================================== + SELECT ("continuation_0_0"."row")."block" AS "continuation_0_0_block", ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", @@ -58,10 +60,12 @@ WHERE AND ("continuation_0_0"."row")."filter" IS NOT FALSE AND TRUE -Compiled parameters: +============================= Compiled Parameters ============================== + $1: TemporalAxis(Transaction) $2: TemporalAxis(Decision) $3: Symbol(name) $4: Input(expected) -Auxiliary parameters: +============================= Auxiliary Parameters ============================= + AuxiliaryParameters { initial_offset: 4, parameters: [] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap new file mode 100644 index 00000000000..b96a0d47112 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap @@ -0,0 +1,95 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs +expression: report +--- +===================================== MIR ====================================== + +fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { + let %2: ? + let %3: ? + let %4: Boolean + + bb0(): { + %2 = %1.properties.name + %3 = input LOAD expected + %4 = %2 == %3 + + return %4 + } +} +===================================== SQL ====================================== + +SELECT + ("continuation_0_0"."row")."block" AS "continuation_0_0_block", + ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", + ("continuation_0_0"."row")."values" AS "continuation_0_0_values" +FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" +INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" + ON + "entity_is_of_type_ids_0_0_2"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +CROSS JOIN LATERAL ( + SELECT + "ee"."entity_edition_id" AS "entity_edition_id", + ( + "ee"."properties" + - ( + CASE + WHEN + $5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls") + THEN ARRAY[$6]::text [] + ELSE ARRAY[]::text [] + END + ) + ) AS "properties", + "ee"."archived" AS "archived", + "ee"."confidence" AS "confidence", + "ee"."provenance" AS "provenance", + ( + "ee"."property_metadata" + - ( + CASE + WHEN + $5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls") + THEN ARRAY[$6]::text [] + ELSE ARRAY[]::text [] + END + ) + ) AS "property_metadata" + FROM "entity_editions" AS "ee" + WHERE + "ee"."entity_edition_id" + = "entity_temporal_metadata_0_0_0"."entity_edition_id" +) AS "entity_editions_0_0_1" +CROSS JOIN LATERAL + ( + SELECT + ( + ROW( + COALESCE( + ( + (TO_JSONB(JSONB_EXTRACT_PATH("entity_editions_0_0_1"."properties", ((($3::text))::text))) = TO_JSONB(($4::jsonb)))::boolean + ), + FALSE + ), + NULL, + NULL, + NULL + )::continuation + ) AS "row" + ) AS "continuation_0_0" +WHERE + "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) + AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) + AND ("continuation_0_0"."row")."filter" IS NOT FALSE + AND TRUE + +============================= Compiled Parameters ============================== + +$1: TemporalAxis(Transaction) +$2: TemporalAxis(Decision) +$3: Symbol(name) +$4: Input(expected) +============================= Auxiliary Parameters ============================= + +AuxiliaryParameters { initial_offset: 4, parameters: ["https://hash.ai/@h/types/entity-type/user/", "https://hash.ai/@h/types/property-type/email/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap index 8db948e9818..32d5da77d1a 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap @@ -2,7 +2,8 @@ source: libs/@local/hashql/eval/src/postgres/authorization/tests.rs expression: report --- -MIR: +===================================== MIR ====================================== + fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { let %2: ? let %3: ? @@ -16,7 +17,8 @@ fn {graph::read::filter@4294967040}(%0: (), %1: Entity) -> Boolean { return %4 } } -SQL: +===================================== SQL ====================================== + SELECT ("continuation_0_0"."row")."block" AS "continuation_0_0_block", ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", @@ -95,10 +97,12 @@ WHERE ) ) -Compiled parameters: +============================= Compiled Parameters ============================== + $1: TemporalAxis(Transaction) $2: TemporalAxis(Decision) $3: Symbol(name) $4: Input(expected) -Auxiliary parameters: +============================= Auxiliary Parameters ============================= + AuxiliaryParameters { initial_offset: 4, parameters: [EntityUuid(11111111-1111-1111-1111-111111111111), WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))), "https://hash.ai/@h/types/entity-type/restricted/", OntologyTypeVersion { major: 1, pre_release: None }, ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_webs.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_webs.snap new file mode 100644 index 00000000000..85d10ebd442 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/optimize_multiple_webs.snap @@ -0,0 +1,8 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +description: "OptimizationData { permitted_entity_uuids: [], permitted_entity_type_uuids: [], permitted_property_type_uuids: [], permitted_data_type_uuids: [], permitted_web_ids: [WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))), WebId(ActorGroupEntityUuid(EntityUuid(22222222-2222-2222-2222-222222222222)))] }" +expression: "snapshot_with_params(&expr[0].transpile_to_string(), &fixture.parameters)" +--- +"entity_temporal_metadata_0_0_0"."web_id" = ANY(($1::uuid[])) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [[WebId(ActorGroupEntityUuid(EntityUuid(33333333-3333-3333-3333-333333333333))), WebId(ActorGroupEntityUuid(EntityUuid(22222222-2222-2222-2222-222222222222)))]] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap new file mode 100644 index 00000000000..0b006b35fec --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap @@ -0,0 +1,8 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "Any([Equal(Path { path: Uuid }, ActorId), In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls })])" +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" +--- +(("entity_temporal_metadata_0_0_0"."entity_uuid" = $1) OR ($2 = ANY("entity_is_of_type_ids_0_0_1"."base_urls"))) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/entity-type/user/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap new file mode 100644 index 00000000000..38150adf654 --- /dev/null +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap @@ -0,0 +1,8 @@ +--- +source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +description: "PropertyProtectionFilterConfig { property_filters: {\"https://hash.ai/@h/types/property-type/email/\": Equal(Path { path: Uuid }, ActorId), \"https://hash.ai/@h/types/property-type/phone/\": NotEqual(Path { path: Uuid }, ActorId)}, embedding_exclusions: {} }" +expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" +--- +(CASE WHEN "entity_temporal_metadata_0_0_0"."entity_uuid" = $1 THEN ARRAY[$2]::text[] ELSE ARRAY[]::text[] END || CASE WHEN "entity_temporal_metadata_0_0_0"."entity_uuid" != $3 THEN ARRAY[$4]::text[] ELSE ARRAY[]::text[] END) + +parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/phone/"] } From 0772247d72aa9acc905f879de1ff89ac500c52c8 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:33:16 +0200 Subject: [PATCH 21/34] feat: checkpoint --- .../authorization/protection/tests.rs | 35 +++++++++++++++---- .../transpile_multiple_properties.snap | 8 ----- 2 files changed, 29 insertions(+), 14 deletions(-) delete mode 100644 libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs index 186aa7fdc01..04da91e4dee 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs @@ -362,11 +362,34 @@ fn transpile_multiple_protected_properties() { let result = fixture.protection().transpile(&policy, &config); let expr = result.keys_to_remove.expect("should produce a mask"); - let mut settings = snapshot_settings(); - settings.set_description(format!("{config:?}")); - let _guard = settings.bind_to_scope(); - assert_snapshot!( - "transpile_multiple_properties", - snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters), + let sql = expr.transpile_to_string(); + let params = format!("{:?}", fixture.parameters); + + // Two properties produce concatenated CASE expressions. + assert!( + sql.contains("||"), + "multiple properties should be concatenated with ||: {sql}", + ); + // Each property contributes one actor UUID param and one property URL param. + assert_eq!( + fixture.parameters.len(), + 4, + "expected 4 params (2 per property): {params}", + ); + + // Both property URLs must appear in the params (order-independent). + assert!( + params.contains("email"), + "params should contain email property URL: {params}", + ); + assert!( + params.contains("phone"), + "params should contain phone property URL: {params}", + ); + + // Both operator shapes must appear (= for email, != for phone). + assert!( + sql.contains("= $") && sql.contains("!= $"), + "should contain both = and != operators: {sql}", ); } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap deleted file mode 100644 index 38150adf654..00000000000 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_multiple_properties.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs -description: "PropertyProtectionFilterConfig { property_filters: {\"https://hash.ai/@h/types/property-type/email/\": Equal(Path { path: Uuid }, ActorId), \"https://hash.ai/@h/types/property-type/phone/\": NotEqual(Path { path: Uuid }, ActorId)}, embedding_exclusions: {} }" -expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" ---- -(CASE WHEN "entity_temporal_metadata_0_0_0"."entity_uuid" = $1 THEN ARRAY[$2]::text[] ELSE ARRAY[]::text[] END || CASE WHEN "entity_temporal_metadata_0_0_0"."entity_uuid" != $3 THEN ARRAY[$4]::text[] ELSE ARRAY[]::text[] END) - -parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/phone/"] } From 5b2e5869ea98ae63d6155bea9e17a314d51252bc Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:53:32 +0200 Subject: [PATCH 22/34] feat: hardening --- .../eval/src/postgres/authorization/mod.rs | 14 +++++ .../src/postgres/authorization/policy/mod.rs | 7 +++ .../eval/src/postgres/authorization/tests.rs | 2 +- .../hashql/eval/src/postgres/prepared.rs | 52 ++++++++++++++++++- 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index db2bf2060a5..cca756b9b8b 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -95,6 +95,20 @@ fn find_from_by_alias<'from, 'id>( } } +/// Patch layer that grafts actor-specific authorization onto a compiled query. +/// +/// Adds two kinds of runtime conditions: +/// +/// - **Policy admission**: permit/forbid conditions appended to WHERE. +/// - **Property masking**: CASE WHEN expressions grafted into the `entity_editions` LATERAL +/// subquery, stripping protected property keys from `properties` and `property_metadata`. +/// +/// This layer must be the innermost in the [`PreparedQueryPatch`] pipeline. +/// It locates the `entity_editions` LATERAL by the alias assigned during +/// compilation. If an outer layer were to introduce its own `entity_editions`, +/// the graft would patch the wrong node and masking would be bypassed. +/// +/// [`PreparedQueryPatch`]: super::PreparedQueryPatch pub struct AuthorizationPatch<'policy, 'path> { policy: &'policy PolicyComponents, properties: &'policy PropertyProtectionFilterConfig<'path>, diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs index 2c4d1cc42c0..e4091e91538 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs @@ -157,6 +157,13 @@ fn convert_entity_resource_filter( /// /// Non-entity resource types (entity types, property types, data types, meta) /// produce `FALSE` since they cannot match entity rows. +/// +/// All referenced columns (`entity_uuid`, `web_id`, `provenance`, +/// `base_urls`) are NOT NULL in the schema, so the generated +/// predicates are two-valued. If nullable columns were ever +/// referenced, forbid negation (`NOT expr`) would need +/// `expr IS NOT TRUE` to remain fail-closed under SQL +/// three-valued logic. fn convert_resource_constraint( unit: &mut PolicyTranslationUnit<'_, A>, constraint: &ResourceConstraint, diff --git a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs index 873c02a7f97..927ec31d42b 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs @@ -449,7 +449,7 @@ fn compile_and_patch<'heap>( "unexpected diagnostics from compilation", ); - let mut patch = PreparedQueryPatch::new().layer(AuthorizationPatch::new(policy, properties)); + let patch = PreparedQueryPatch::new().layer(AuthorizationPatch::new(policy, properties)); patch.apply(&mut prepared_query, Global); let body = format_body(fixture, heap); diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs index 5b0139fe8fe..e8f352d60a0 100644 --- a/libs/@local/hashql/eval/src/postgres/prepared.rs +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -39,19 +39,39 @@ impl PreparedQuery<'_, A> { } } +/// Cons cell for the patch layer list. +/// +/// Layers are pushed via [`PreparedQueryPatch::layer`] and execute +/// outermost-first: the last layer added runs first and receives the +/// rest of the list as its `next` continuation. pub struct HCons { head: H, tail: T, } +/// Terminal element of the patch layer list. +/// +/// When reached, materializes all auxiliary joins registered during +/// the preceding layers by calling +/// [`AuxiliaryProjections::build_joins`]. pub struct HNil; +/// Shared mutable context threaded through every patch layer. +/// +/// Layers register projection demands (auxiliary joins) here; `HNil` +/// materializes them into the query's FROM clause at the end of the +/// chain. pub struct PatchContext<'ctx, A: Allocator> { pub projections: AuxiliaryProjections, pub alloc: A, _marker: PhantomData<&'ctx ()>, } +/// A composed patch chain that can be applied to a [`PreparedQuery`]. +/// +/// Implemented by [`HNil`] (which materializes joins) and by +/// [`HCons`] (which delegates to its head layer, passing the tail +/// as the `next` continuation). pub trait PatchPreparedQuery { fn patch_query( &mut self, @@ -61,6 +81,17 @@ pub trait PatchPreparedQuery { ); } +/// A single patch layer in the continuation-passing pipeline. +/// +/// Each layer receives a `next` continuation and must call +/// `next.patch_query()` exactly once. Work done before that call +/// can register projection demands and modify the WHERE clause; +/// work done after can inspect and rewrite the materialized FROM +/// tree. +/// +/// Skipping the `next` call prevents join materialization and +/// blocks all inner layers. Calling it more than once duplicates +/// joins and conditions. pub trait PatchPreparedQueryLayer { fn patch_query( &mut self, @@ -106,6 +137,17 @@ where } } +/// Builder for a [`PatchPreparedQueryLayer`] pipeline. +/// +/// Layers are added with [`layer`](Self::layer) and execute outermost-first: +/// the last layer added runs first. The pipeline terminates at [`HNil`], +/// which materializes all auxiliary joins accumulated by the layers. +/// +/// ```text +/// PreparedQueryPatch::new() // HNil (join materialization) +/// .layer(authorization) // runs first, calls next for joins +/// .apply(&mut query, scratch); +/// ``` pub struct PreparedQueryPatch { patches: T, } @@ -118,6 +160,9 @@ impl PreparedQueryPatch { } impl PreparedQueryPatch { + /// Wraps the current pipeline with an additional outer layer. + /// + /// The new layer executes before all previously added layers. pub fn layer(self, other: T2) -> PreparedQueryPatch> { PreparedQueryPatch { patches: HCons { @@ -127,8 +172,13 @@ impl PreparedQueryPatch { } } + /// Runs the full patch pipeline against `query`. + /// + /// Constructs [`AuxiliaryProjections`] from the query's compiled + /// projections, then invokes the layer chain. The terminal [`HNil`] + /// materializes all registered auxiliary joins into the FROM clause. pub fn apply( - &mut self, + mut self, query: &mut PreparedQuery, scratch: S, ) where From edc8804ab66c954c4580199e5aec5bdb82b02ddc Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:24:31 +0200 Subject: [PATCH 23/34] feat: checkpoint --- apps/hash-graph/src/subcommand/server.rs | 19 +-- .../graph/api/src/rest/hashql/compile.rs | 49 +++----- .../@local/graph/api/src/rest/hashql/error.rs | 110 +++++++++++++++++- libs/@local/graph/api/src/rest/hashql/mod.rs | 82 +++++++------ libs/@local/graph/api/src/rest/mod.rs | 10 +- libs/@local/graph/type-fetcher/src/store.rs | 9 ++ .../eval/src/postgres/authorization/mod.rs | 11 +- .../hashql/eval/src/postgres/prepared.rs | 60 ++++++++-- .../postgres/email_filter_protection.rs | 28 ++--- 9 files changed, 255 insertions(+), 123 deletions(-) diff --git a/apps/hash-graph/src/subcommand/server.rs b/apps/hash-graph/src/subcommand/server.rs index 196f94d8a99..a2a7e1d2744 100644 --- a/apps/hash-graph/src/subcommand/server.rs +++ b/apps/hash-graph/src/subcommand/server.rs @@ -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; @@ -383,7 +383,6 @@ where /// Starts the main graph API server (REST + optional RPC). async fn start_server( pool: S, - postgres: PostgresStorePool, compiler: Arc, config: ServerConfig, query_logger: Option, @@ -391,7 +390,7 @@ async fn start_server( ) -> Result<(), Report> where S: StorePool + Send + Sync + 'static, - for<'p> S::Store<'p>: RestApiStore + PrincipalStore + PolicyStore, + for<'p> S::Store<'p>: RestApiStore + PrincipalStore + PolicyStore + AsRef, { let store = Arc::new(pool); let temporal_client = create_temporal_client(&config.temporal) @@ -414,7 +413,6 @@ where let router = rest_api_router(RestRouterDependencies { store, - postgres, temporal_client, domain_regex: DomainValidator::new(config.allowed_url_domain), query_logger, @@ -478,8 +476,6 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report> { let lifecycle = ServerLifecycle::new(); - let postgres = pool.clone(); - if args.embed_admin { start_admin_server(pool.clone(), args.admin, &lifecycle); } @@ -521,16 +517,7 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report> { args.config.compiler.compiler_exec_pool_size.get(), )); - if let Err(error) = start_server( - pool, - postgres, - compiler, - args.config, - query_logger, - &lifecycle, - ) - .await - { + if let Err(error) = start_server(pool, compiler, args.config, query_logger, &lifecycle).await { lifecycle.shutdown_and_wait().await; return Err(error); } diff --git a/libs/@local/graph/api/src/rest/hashql/compile.rs b/libs/@local/graph/api/src/rest/hashql/compile.rs index cf1a9cde86c..f871e725fed 100644 --- a/libs/@local/graph/api/src/rest/hashql/compile.rs +++ b/libs/@local/graph/api/src/rest/hashql/compile.rs @@ -17,35 +17,15 @@ use hashql_mir::{ body::Body, def::{DefId, DefIdVec}, error::MirDiagnosticCategory, - pass::{LowerConfig, execution::ExecutionAnalysisResidual}, - visit::Visitor, + pass::{ + LowerConfig, + execution::{ExecutionAnalysisResidual, VertexType}, + }, }; use hashql_syntax_jexpr::span::Span; use super::error::HashQlDiagnosticCategory; -struct FindActions<'heap> { - actions: heap::Vec<'heap, ActionName>, -} - -impl<'heap> hashql_mir::visit::Visitor<'heap> for FindActions<'heap> { - type Result = Result<(), !>; - - fn visit_graph_read_head( - &mut self, - _: hashql_mir::body::terminator::GraphReadLocation, - head: &hashql_mir::body::terminator::GraphReadHead<'heap>, - ) -> Self::Result { - match head { - hashql_mir::body::terminator::GraphReadHead::Entity { axis: _ } => { - self.actions.push(ActionName::ViewEntity); - } - } - - Ok(()) - } -} - pub(crate) struct CodeCompilationArtifact<'heap> { pub assignment: DefIdVec>, &'heap Heap>, @@ -164,17 +144,6 @@ impl<'heap> Compilation<'heap> { .map_category(HashQlDiagnosticCategory::Mir) .with_diagnostics(advisories)?; - let mut actions = FindActions { - actions: heap::Vec::new_in(heap), - }; - for body in &bodies { - Ok(()) = actions.visit_body(body) - } - - let permissions = CodeExecutionPermissions { - actions: actions.actions, - }; - // Plan the execution let Success { value: execution, @@ -198,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(()) diff --git a/libs/@local/graph/api/src/rest/hashql/error.rs b/libs/@local/graph/api/src/rest/hashql/error.rs index 0fb9d7ed609..031e0e5516d 100644 --- a/libs/@local/graph/api/src/rest/hashql/error.rs +++ b/libs/@local/graph/api/src/rest/hashql/error.rs @@ -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; 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, @@ -67,6 +69,112 @@ impl DiagnosticCategory for HashQlDiagnosticCategory { } } +pub(crate) fn store_acquire_diagnostic( + report: &Report, + root_span: SpanId, +) -> Diagnostic { + 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, + root_span: SpanId, +) -> Diagnostic { + match report.current_context() { + ContextCreationError::ActorNotFound { actor_id } => { + actor_not_found(report, root_span, actor_id) + } + ContextCreationError::DetermineActor { actor_id } => { + 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), + } +} + +fn actor_not_found( + report: &Report, + root_span: SpanId, + actor_id: &impl core::fmt::Display, +) -> Diagnostic { + 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, + root_span: SpanId, +) -> Diagnostic { + 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, + report: &Report, + 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, diff --git a/libs/@local/graph/api/src/rest/hashql/mod.rs b/libs/@local/graph/api/src/rest/hashql/mod.rs index bd5f463734f..bbeed025524 100644 --- a/libs/@local/graph/api/src/rest/hashql/mod.rs +++ b/libs/@local/graph/api/src/rest/hashql/mod.rs @@ -16,27 +16,27 @@ use std::thread::available_parallelism; use axum::{Extension, Router, response::IntoResponse as _, routing::post}; use hash_graph_authorization::policies::{ MergePolicies, PolicyComponents, - action::ActionName, store::{PolicyStore, PrincipalStore}, }; -use hash_graph_postgres_store::store::{PostgresStorePool, postgres::PostgresClient}; -use hash_graph_store::pool::StorePool; +use hash_graph_postgres_store::store::postgres::PostgresClient; +use hash_graph_store::{filter::protection::PropertyProtectionFilterConfig, pool::StorePool}; use hash_temporal_client::TemporalClient; use hashql_core::{ - heap::{HeapPool, ScratchPool}, + heap::{HeapPool, ResetAllocator as _, ScratchPool}, span::{SpanId, SpanTable}, }; -use hashql_diagnostics::{ - Diagnostic, IntoStatus as _, Label, Message, Source, Sources, Status, StatusExt as _, Success, - severity::Critical, +use hashql_diagnostics::{IntoStatus as _, Source, Sources, Status, StatusExt as _, Success}; +use hashql_eval::{ + error::EvalDiagnosticCategory, + orchestrator::Orchestrator, + postgres::{AuthorizationPatch, PreparedQueryPatch}, }; -use hashql_eval::{error::EvalDiagnosticCategory, orchestrator::Orchestrator}; use hashql_mir::interpret::Inputs; use hashql_syntax_jexpr::span::Span; use http::StatusCode; use serde_json::value::RawValue; use tokio_util::task::LocalPoolHandle; -use type_system::principal::actor::{ActorEntityUuid, ActorId}; +use type_system::principal::actor::ActorEntityUuid; use utoipa::OpenApi; use self::{ @@ -111,12 +111,10 @@ where let inputs = Inputs::new(); // TODO: https://linear.app/hash/issue/BE-41/hashql-expose-input-in-graph-api let Success { - value: compilation, + value: mut compilation, advisories, } = Compilation::compile(&heap, &mut scratch, spans, query)?; - let context = compilation.context(); - let Success { value: store, advisories, @@ -124,20 +122,7 @@ where .store .acquire(exec.temporal.clone()) .await - .map_err(|report| { - let mut diagnostic = - Diagnostic::new(HashQlDiagnosticCategory::Infrastructure, Critical::BUG).primary( - Label::new(compilation.root_span, "failed to acquire postgres client"), - ); - - if cfg!(debug_assertions) { - diagnostic.add_message(Message::note(format!("{report:?}"))); - } else { - tracing::error!(?report, "failed to acquire postgres client"); - } - - diagnostic - }) + .map_err(|report| error::store_acquire_diagnostic(&report, compilation.root_span)) .into_status() .with_diagnostics(advisories)?; @@ -146,10 +131,29 @@ where compilation.permissions.actions.iter().copied(), MergePolicies::Yes, ); - let policy_components = policy_components + let Success { + value: policy_components, + advisories, + } = policy_components .await - .map_err(|report| todo!("proper diagnostic"))?; + .map_err(|report| error::authorization_context_diagnostic(&report, compilation.root_span)) + .into_status() + .with_diagnostics(advisories)?; + let property = PropertyProtectionFilterConfig::hash_default(); + let patch = AuthorizationPatch::new(&policy_components, &property); + + // TODO: in the future when we cache queries, this will have to clone them, but because this is + // oneshot, we can just ignore that for now. + for query in compilation.artifact.postgres.iter_mut() { + PreparedQueryPatch::new() + .layer(&patch) + .apply(query, &mut *scratch); + + scratch.reset(); + } + + let context = compilation.context(); let orchestrator = Orchestrator::new(&store, &compilation.artifact.postgres, &context); orchestrator .run(&inputs, compilation.entrypoint, []) @@ -170,7 +174,7 @@ async fn query_local( options: CompilationOutputOptions, ) -> BoxedResponse where - S: for<'pool> StorePool: AsRef>, + S: for<'pool> StorePool: AsRef + PrincipalStore + PolicyStore>, { let mut sources = Sources::new(); let source_id = sources.push(Source::new(query.get())); @@ -189,7 +193,10 @@ async fn run_query( options: CompilationOutputOptions, ) -> BoxedResponse where - S: for<'pool> StorePool: AsRef> + Send + Sync + 'static, + S: for<'pool> StorePool: AsRef + PrincipalStore + PolicyStore> + + Send + + Sync + + 'static, { // The compiler and interpreter hold references into bump-allocated heaps, making their // futures `!Send`. `spawn_pinned` runs them on a dedicated thread; the returned handle @@ -265,7 +272,10 @@ pub(crate) async fn query_hashql( Json(request): Json, ) -> BoxedResponse where - S: for<'pool> StorePool: AsRef> + Send + Sync + 'static, + S: for<'pool> StorePool: AsRef + PrincipalStore + PolicyStore> + + Send + + Sync + + 'static, { let exec = ExecutionContext { temporal, @@ -290,7 +300,13 @@ where pub(crate) struct HashQlResource; impl HashQlResource { - pub(crate) fn routes() -> Router { - Router::new().route("/hashql", post(query_hashql)) + pub(crate) fn routes() -> Router + where + S: for<'pool> StorePool: AsRef + PrincipalStore + PolicyStore> + + Send + + Sync + + 'static, + { + Router::new().route("/hashql", post(query_hashql::)) } } diff --git a/libs/@local/graph/api/src/rest/mod.rs b/libs/@local/graph/api/src/rest/mod.rs index 3d8d7075b8a..4c2a9208d42 100644 --- a/libs/@local/graph/api/src/rest/mod.rs +++ b/libs/@local/graph/api/src/rest/mod.rs @@ -38,7 +38,9 @@ use error_stack::{Report, ResultExt as _}; use futures::{SinkExt as _, channel::mpsc::Sender}; use hash_codec::numeric::Real; use hash_graph_authorization::policies::store::{PolicyStore, PrincipalStore}; -use hash_graph_postgres_store::store::{PostgresStorePool, error::VersionedUrlAlreadyExists}; +use hash_graph_postgres_store::store::{ + error::VersionedUrlAlreadyExists, postgres::PostgresClient, +}; use hash_graph_store::{ account::AccountStore, data_type::DataTypeStore, @@ -260,7 +262,7 @@ static STATIC_SCHEMAS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src/rest/json fn api_resources() -> Vec where S: StorePool + Send + Sync + 'static, - for<'pool> S::Store<'pool>: RestApiStore + PrincipalStore + PolicyStore, + for<'pool> S::Store<'pool>: RestApiStore + PrincipalStore + PolicyStore + AsRef, { vec![ data_type::DataTypeResource::routes::(), @@ -269,6 +271,7 @@ where entity::EntityResource::routes::(), permissions::PermissionResource::routes::(), principal::PrincipalResource::routes::(), + hashql::HashQlResource::routes::(), ] } @@ -452,13 +455,12 @@ pub fn openapi_only_router() -> Router { pub fn rest_api_router(dependencies: RestRouterDependencies) -> Router where S: StorePool + Send + Sync + 'static, - for<'p> S::Store<'p>: RestApiStore + PrincipalStore + PolicyStore, + for<'p> S::Store<'p>: RestApiStore + PrincipalStore + PolicyStore + AsRef, { // All api resources are merged together into a super-router. let merged_routes = api_resources::() .into_iter() .fold(Router::new(), Router::merge) - .merge(hashql::HashQlResource::routes()) .fallback(|| { tracing::error!("404: Not found"); async { StatusCode::NOT_FOUND } diff --git a/libs/@local/graph/type-fetcher/src/store.rs b/libs/@local/graph/type-fetcher/src/store.rs index 799c5a4c1c4..6737711e54a 100644 --- a/libs/@local/graph/type-fetcher/src/store.rs +++ b/libs/@local/graph/type-fetcher/src/store.rs @@ -177,6 +177,15 @@ pub struct FetchingStore { connection_info: Option>, } +impl AsRef for FetchingStore +where + S: AsRef, +{ + fn as_ref(&self) -> &T { + self.store.as_ref() + } +} + impl PrincipalStore for FetchingStore where S: PrincipalStore + Send + Sync, diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index cca756b9b8b..ca9d843fbae 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -19,7 +19,10 @@ use self::{ policy::{PolicyTranslation, PolicyTranslationUnit}, protection::ProtectionTranslationUnit, }; -use super::{PatchPreparedQueryLayer, prepared::PatchContext}; +use super::{ + PatchPreparedQueryLayer, + prepared::{PatchContext, PatchPreparedQuery}, +}; mod policy; mod protection; @@ -128,13 +131,13 @@ impl PatchPreparedQueryLayer for AuthorizationPatch<'_, '_> { fn patch_query( - &mut self, + &self, context: &mut PatchContext<'_, A>, query: &mut super::PreparedQuery<'_, A>, scratch: S, - next: &mut N, + next: &N, ) where - N: super::prepared::PatchPreparedQuery, + N: PatchPreparedQuery, { let mut policy = PolicyTranslationUnit { projections: &mut context.projections, diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs index e8f352d60a0..e7ec46a2d7d 100644 --- a/libs/@local/hashql/eval/src/postgres/prepared.rs +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -74,13 +74,29 @@ pub struct PatchContext<'ctx, A: Allocator> { /// as the `next` continuation). pub trait PatchPreparedQuery { fn patch_query( - &mut self, + &self, context: &mut PatchContext<'_, A>, query: &mut PreparedQuery<'_, A>, scratch: S, ); } +impl PatchPreparedQuery for &T +where + A: Allocator, + S: Allocator, + T: PatchPreparedQuery, +{ + fn patch_query( + &self, + context: &mut PatchContext<'_, A>, + query: &mut PreparedQuery<'_, A>, + scratch: S, + ) { + T::patch_query(self, context, query, scratch); + } +} + /// A single patch layer in the continuation-passing pipeline. /// /// Each layer receives a `next` continuation and must call @@ -94,18 +110,37 @@ pub trait PatchPreparedQuery { /// joins and conditions. pub trait PatchPreparedQueryLayer { fn patch_query( - &mut self, + &self, context: &mut PatchContext<'_, A>, query: &mut PreparedQuery<'_, A>, scratch: S, - next: &mut N, + next: &N, ) where N: PatchPreparedQuery; } +impl PatchPreparedQueryLayer for &T +where + A: Allocator, + S: Allocator, + T: PatchPreparedQueryLayer, +{ + fn patch_query( + &self, + context: &mut PatchContext<'_, A>, + query: &mut PreparedQuery<'_, A>, + scratch: S, + next: &N, + ) where + N: PatchPreparedQuery, + { + T::patch_query(self, context, query, scratch, next); + } +} + impl PatchPreparedQuery for HNil { fn patch_query( - &mut self, + &self, context: &mut PatchContext<'_, A>, query: &mut PreparedQuery<'_, A>, _: S, @@ -126,7 +161,7 @@ where T: PatchPreparedQuery, { fn patch_query( - &mut self, + &self, context: &mut PatchContext<'_, A>, query: &mut PreparedQuery<'_, A>, scratch: S, @@ -177,11 +212,8 @@ impl PreparedQueryPatch { /// Constructs [`AuxiliaryProjections`] from the query's compiled /// projections, then invokes the layer chain. The terminal [`HNil`] /// materializes all registered auxiliary joins into the FROM clause. - pub fn apply( - mut self, - query: &mut PreparedQuery, - scratch: S, - ) where + pub fn apply(self, query: &mut PreparedQuery, scratch: S) + where T: PatchPreparedQuery, { let alloc = query.columns.allocator().clone(); @@ -227,4 +259,12 @@ impl<'heap, A: Allocator> PreparedQueries<'heap, A> { .find(|(id, _)| *id == block) .map(|(_, query)| query) } + + pub fn iter(&self) -> impl Iterator> { + self.queries.iter().map(|(_, query)| query) + } + + pub fn iter_mut(&mut self) -> impl Iterator> { + self.queries.iter_mut().map(|(_, query)| query) + } } diff --git a/tests/graph/integration/postgres/email_filter_protection.rs b/tests/graph/integration/postgres/email_filter_protection.rs index 6e17b025d40..d3f5a795fa7 100644 --- a/tests/graph/integration/postgres/email_filter_protection.rs +++ b/tests/graph/integration/postgres/email_filter_protection.rs @@ -30,8 +30,8 @@ use hash_graph_store::{ filter::{ Filter, FilterExpression, JsonPath, Parameter, PathToken, protection::{ - PropertyFilter, PropertyFilterExpression, PropertyFilterExpressionList, - PropertyProtectionFilterConfig, + PropertyFilter, PropertyFilterEntityQueryPath, PropertyFilterExpression, + PropertyFilterExpressionList, PropertyProtectionFilterConfig, }, }, query::{NullOrdering, Ordering}, @@ -1428,16 +1428,12 @@ fn multi_property_config() -> PropertyProtectionFilterConfig<'static> { parameter: Parameter::Text(Cow::Borrowed(USER_ENTITY_TYPE_BASE_URL)), }, PropertyFilterExpressionList::Path { - path: EntityQueryPath::EntityTypeEdge { - edge_kind: SharedEdgeKind::IsOfType, - path: EntityTypeQueryPath::BaseUrl, - inheritance_depth: None, - }, + path: PropertyFilterEntityQueryPath::TypeBaseUrls, }, ), PropertyFilter::NotEqual( PropertyFilterExpression::Path { - path: EntityQueryPath::Uuid, + path: PropertyFilterEntityQueryPath::Uuid, }, PropertyFilterExpression::ActorId, ), @@ -2054,16 +2050,12 @@ fn multi_type_config() -> PropertyProtectionFilterConfig<'static> { parameter: Parameter::Text(Cow::Borrowed(USER_ENTITY_TYPE_BASE_URL)), }, PropertyFilterExpressionList::Path { - path: EntityQueryPath::EntityTypeEdge { - edge_kind: SharedEdgeKind::IsOfType, - path: EntityTypeQueryPath::BaseUrl, - inheritance_depth: None, - }, + path: PropertyFilterEntityQueryPath::TypeBaseUrls, }, ), PropertyFilter::NotEqual( PropertyFilterExpression::Path { - path: EntityQueryPath::Uuid, + path: PropertyFilterEntityQueryPath::Uuid, }, PropertyFilterExpression::ActorId, ), @@ -2077,16 +2069,12 @@ fn multi_type_config() -> PropertyProtectionFilterConfig<'static> { parameter: Parameter::Text(Cow::Borrowed(SECRET_ENTITY_TYPE_BASE_URL)), }, PropertyFilterExpressionList::Path { - path: EntityQueryPath::EntityTypeEdge { - edge_kind: SharedEdgeKind::IsOfType, - path: EntityTypeQueryPath::BaseUrl, - inheritance_depth: None, - }, + path: PropertyFilterEntityQueryPath::TypeBaseUrls, }, ), PropertyFilter::NotEqual( PropertyFilterExpression::Path { - path: EntityQueryPath::Uuid, + path: PropertyFilterEntityQueryPath::Uuid, }, PropertyFilterExpression::ActorId, ), From b31f4caebbd34ce8b5f845a5d871d0a4fc93ee30 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:51:18 +0200 Subject: [PATCH 24/34] fix: inject auxiliary requests --- .../src/orchestrator/request/graph_read.rs | 9 ++- .../eval/src/orchestrator/request/iter.rs | 61 ++++++++++++++++ .../eval/src/orchestrator/request/mod.rs | 1 + .../hashql/eval/src/postgres/parameters.rs | 4 ++ .../hashql/eval/src/postgres/prepared.rs | 7 ++ tests/graph/http/tests/hashql.http | 69 +++++++++++++++++++ 6 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 libs/@local/hashql/eval/src/orchestrator/request/iter.rs diff --git a/libs/@local/hashql/eval/src/orchestrator/request/graph_read.rs b/libs/@local/hashql/eval/src/orchestrator/request/graph_read.rs index da996456fac..fbb7b64a900 100644 --- a/libs/@local/hashql/eval/src/orchestrator/request/graph_read.rs +++ b/libs/@local/hashql/eval/src/orchestrator/request/graph_read.rs @@ -32,6 +32,7 @@ use hashql_mir::{ }; use tokio_postgres::{Client, Row}; +use super::iter::ExactSizeAdapter; use crate::{ orchestrator::{ Indexed, Orchestrator, @@ -410,7 +411,13 @@ impl<'or, 'env, 'ctx, 'heap, C: AsRef, E: EventLog, A: Allocator> .inner .client .as_ref() - .query_raw(&statement, params.iter().map(|param| &**param)) + .query_raw( + &statement, + ExactSizeAdapter::from_chain( + params.iter().map(|param| &**param), + query.auxiliary_parameters().into_iter(), + ), + ) .await .map_err(|source| BridgeError::QueryExecution { sql: statement.clone(), diff --git a/libs/@local/hashql/eval/src/orchestrator/request/iter.rs b/libs/@local/hashql/eval/src/orchestrator/request/iter.rs new file mode 100644 index 00000000000..338a70cd5f4 --- /dev/null +++ b/libs/@local/hashql/eval/src/orchestrator/request/iter.rs @@ -0,0 +1,61 @@ +use core::iter; + +/// Adapter that adds [`ExactSizeIterator`] semantics to an iterator with known length. +pub(crate) struct ExactSizeAdapter { + iter: I, + len: usize, +} + +impl ExactSizeAdapter { + pub(crate) fn from_chain(first: I, second: J) -> ExactSizeAdapter> + where + I: ExactSizeIterator, + J: ExactSizeIterator, + { + let len = first.len() + second.len(); + + ExactSizeAdapter { + iter: first.chain(second), + len, + } + } +} + +impl Iterator for ExactSizeAdapter +where + I: Iterator, +{ + type Item = I::Item; + + fn next(&mut self) -> Option { + let item = self.iter.next()?; + self.len -= 1; + + Some(item) + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl DoubleEndedIterator for ExactSizeAdapter +where + I: DoubleEndedIterator, +{ + fn next_back(&mut self) -> Option { + let item = self.iter.next_back()?; + self.len -= 1; + + Some(item) + } +} + +impl ExactSizeIterator for ExactSizeAdapter +where + I: Iterator, +{ + fn len(&self) -> usize { + self.len + } +} diff --git a/libs/@local/hashql/eval/src/orchestrator/request/mod.rs b/libs/@local/hashql/eval/src/orchestrator/request/mod.rs index a274e6d7a1e..db5ff9828ce 100644 --- a/libs/@local/hashql/eval/src/orchestrator/request/mod.rs +++ b/libs/@local/hashql/eval/src/orchestrator/request/mod.rs @@ -7,4 +7,5 @@ //! [`GraphRead`]: hashql_mir::body::terminator::GraphRead mod graph_read; +mod iter; pub(crate) use self::graph_read::GraphReadOrchestrator; diff --git a/libs/@local/hashql/eval/src/postgres/parameters.rs b/libs/@local/hashql/eval/src/postgres/parameters.rs index 1204857e2b3..d52c7356c52 100644 --- a/libs/@local/hashql/eval/src/postgres/parameters.rs +++ b/libs/@local/hashql/eval/src/postgres/parameters.rs @@ -282,6 +282,10 @@ impl AuxiliaryParameters { pub(crate) fn len(&self) -> usize { self.parameters.len() } + + pub(crate) fn iter(&self) -> impl ExactSizeIterator { + self.parameters.iter().map(|param| &**param) + } } #[cfg(test)] diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs index e7ec46a2d7d..94c4821c542 100644 --- a/libs/@local/hashql/eval/src/postgres/prepared.rs +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -11,6 +11,7 @@ use hashql_mir::{ def::{DefId, DefIdSlice}, pass::execution::VertexType, }; +use postgres_types::ToSql; use super::{ ColumnDescriptor, Parameters, @@ -37,6 +38,12 @@ impl PreparedQuery<'_, A> { pub fn transpile(&self) -> impl Display { core::fmt::from_fn(|fmt| self.statement.transpile(fmt)) } + + pub fn auxiliary_parameters( + &self, + ) -> impl IntoIterator { + self.auxiliary_parameters.iter() + } } /// Cons cell for the patch layer list. diff --git a/tests/graph/http/tests/hashql.http b/tests/graph/http/tests/hashql.http index 5dfe5041548..d403802c77e 100644 --- a/tests/graph/http/tests/hashql.http +++ b/tests/graph/http/tests/hashql.http @@ -38,9 +38,74 @@ X-Authenticated-User-Actor-Id: {{system_machine_id}} client.global.set("user_id", response.body.userId); %} +### HashQL: missing auth header returns 400 +POST http://127.0.0.1:4000/hashql +Content-Type: application/json + +{ + "query": ["#literal", 1], + "inputs": [] +} + +> {% + client.test("status", function() { + client.assert(response.status === 400, "Expected 400 for missing auth header"); + }); + client.test("mentions header", function() { + client.assert(response.body.includes("X-Authenticated-User-Actor-Id"), + "Expected response to mention missing header name"); + }); +%} + +### HashQL: invalid actor returns 400 with diagnostic +POST http://127.0.0.1:4000/hashql +Content-Type: application/json +X-Authenticated-User-Actor-Id: ffffffff-ffff-ffff-ffff-ffffffffffff + +{ + "query": ["let", "axes", + ["::graph::temporal::PinnedTransactionTimeTemporalAxes", + {"#struct": { + "pinned": ["::graph::temporal::TransactionTime", + ["::graph::temporal::Timestamp", {"#literal": 4102444800000}] + ], + "variable": ["::graph::temporal::DecisionTime", + ["::graph::temporal::Interval", {"#struct": { + "start": ["::graph::temporal::UnboundedTemporalBound"], + "end": ["::graph::temporal::ExclusiveTemporalBound", + ["::graph::temporal::Timestamp", {"#literal": 4102444800000}] + ] + }}] + ] + }} + ], + ["::graph::tail::collect", + ["::graph::body::filter", + ["::graph::head::entities", "axes"], + ["fn", {"#tuple": []}, {"#struct": {"vertex": "_"}}, "_", + {"#literal": false} + ] + ] + ] + ], + "inputs": [] +} + +> {% + client.test("status", function() { + client.assert(response.status === 400, "Expected 400 for invalid actor"); + }); + client.test("has diagnostic", function() { + client.assert(response.body.primary !== undefined, "Expected primary diagnostic"); + client.assert(response.body.primary.labels.labels[0].message.includes("does not exist"), + "Expected diagnostic to mention actor does not exist"); + }); +%} + ### HashQL: filter-false returns empty list POST http://127.0.0.1:4000/hashql Content-Type: application/json +X-Authenticated-User-Actor-Id: {{user_id}} { "query": ["let", "axes", @@ -86,6 +151,7 @@ Content-Type: application/json ### HashQL: parse error returns 400 with diagnostics POST http://127.0.0.1:4000/hashql Content-Type: application/json +X-Authenticated-User-Actor-Id: {{user_id}} { "query": {"not": "a valid query"}, @@ -105,6 +171,7 @@ Content-Type: application/json ### HashQL: missing inputs field returns 400 POST http://127.0.0.1:4000/hashql Content-Type: application/json +X-Authenticated-User-Actor-Id: {{user_id}} { "query": {"#literal": 1} @@ -124,6 +191,7 @@ Content-Type: application/json ### HashQL: json-compat wraps result in envelope with advisories POST http://127.0.0.1:4000/hashql Content-Type: application/json +X-Authenticated-User-Actor-Id: {{user_id}} Json-Compat: true { @@ -170,6 +238,7 @@ Json-Compat: true ### HashQL: interactive header returns HTML on error POST http://127.0.0.1:4000/hashql Content-Type: application/json +X-Authenticated-User-Actor-Id: {{user_id}} Interactive: true { From 23fee1aff11ca5fc26482adbaf7632812ada5672 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:03:50 +0200 Subject: [PATCH 25/34] fix: suggestions from bugbot --- apps/hash-graph/src/subcommand/server.rs | 19 ++++++++++++++++--- .../@local/graph/api/src/rest/hashql/error.rs | 14 ++++++++++++-- libs/@local/graph/api/src/rest/hashql/mod.rs | 8 ++++++-- libs/@local/graph/api/src/rest/mod.rs | 6 ++++-- .../postgres-store/src/store/postgres/mod.rs | 4 ++-- .../postgres/email_filter_protection.rs | 11 ++++++----- 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/apps/hash-graph/src/subcommand/server.rs b/apps/hash-graph/src/subcommand/server.rs index a2a7e1d2744..739b2aceb8c 100644 --- a/apps/hash-graph/src/subcommand/server.rs +++ b/apps/hash-graph/src/subcommand/server.rs @@ -386,6 +386,7 @@ async fn start_server( compiler: Arc, config: ServerConfig, query_logger: Option, + filter_protection: Arc>, lifecycle: &ServerLifecycle, ) -> Result<(), Report> where @@ -418,6 +419,7 @@ where query_logger, api_config: config.api_config, compiler, + filter_protection, }); start_rest_server(router, config.http_address, lifecycle); @@ -454,9 +456,9 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report> { 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()) }, }, ) @@ -484,6 +486,8 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report> { start_type_fetcher(args.type_fetcher.clone(), &lifecycle); } + let filter_protection = Arc::clone(&pool.settings.filter_protection); + let pool = FetchingPool::new( pool, ( @@ -517,7 +521,16 @@ pub async fn server(mut args: ServerArgs) -> Result<(), Report> { args.config.compiler.compiler_exec_pool_size.get(), )); - if let Err(error) = start_server(pool, compiler, args.config, query_logger, &lifecycle).await { + if let Err(error) = start_server( + pool, + compiler, + args.config, + query_logger, + filter_protection, + &lifecycle, + ) + .await + { lifecycle.shutdown_and_wait().await; return Err(error); } diff --git a/libs/@local/graph/api/src/rest/hashql/error.rs b/libs/@local/graph/api/src/rest/hashql/error.rs index 031e0e5516d..3f02f198cc5 100644 --- a/libs/@local/graph/api/src/rest/hashql/error.rs +++ b/libs/@local/graph/api/src/rest/hashql/error.rs @@ -3,7 +3,7 @@ use core::ops::Range; use axum::response::{Html, IntoResponse as _}; use error_stack::Report; -use hash_graph_authorization::policies::store::error::ContextCreationError; +use hash_graph_authorization::policies::store::error::{ContextCreationError, DetermineActorError}; use hashql_ast::error::AstDiagnosticCategory; use hashql_core::span::{SpanId, SpanTable}; use hashql_diagnostics::{ @@ -101,7 +101,17 @@ pub(crate) fn authorization_context_diagnostic( actor_not_found(report, root_span, actor_id) } ContextCreationError::DetermineActor { actor_id } => { - actor_not_found(report, root_span, 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::() + .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 { .. } diff --git a/libs/@local/graph/api/src/rest/hashql/mod.rs b/libs/@local/graph/api/src/rest/hashql/mod.rs index bbeed025524..fcb5cf677dc 100644 --- a/libs/@local/graph/api/src/rest/hashql/mod.rs +++ b/libs/@local/graph/api/src/rest/hashql/mod.rs @@ -81,6 +81,7 @@ struct ExecutionContext { temporal: Option>, actor_id: ActorEntityUuid, store: Arc, + filter_protection: Arc>, } /// Controls the response format for a HashQL query. @@ -139,9 +140,9 @@ where .map_err(|report| error::authorization_context_diagnostic(&report, compilation.root_span)) .into_status() .with_diagnostics(advisories)?; - let property = PropertyProtectionFilterConfig::hash_default(); + let property = &*exec.filter_protection; - let patch = AuthorizationPatch::new(&policy_components, &property); + let patch = AuthorizationPatch::new(&policy_components, property); // TODO: in the future when we cache queries, this will have to clone them, but because this is // oneshot, we can just ignore that for now. @@ -262,11 +263,13 @@ pub(crate) struct HashQlRequest { (status = 500, description = "Internal compiler or database error"), ) )] +#[expect(clippy::too_many_arguments, reason = "axum handler extractors")] pub(crate) async fn query_hashql( AuthenticatedUserHeader(actor_id): AuthenticatedUserHeader, Extension(store_pool): Extension>, Extension(compiler): Extension>, Extension(temporal): Extension>>, + Extension(filter_protection): Extension>>, InteractiveHeader(interactive): InteractiveHeader, JsonCompatHeader(json_compat): JsonCompatHeader, Json(request): Json, @@ -281,6 +284,7 @@ where temporal, actor_id, store: store_pool, + filter_protection, }; let options = CompilationOutputOptions { diff --git a/libs/@local/graph/api/src/rest/mod.rs b/libs/@local/graph/api/src/rest/mod.rs index 4c2a9208d42..dcd728bf7ae 100644 --- a/libs/@local/graph/api/src/rest/mod.rs +++ b/libs/@local/graph/api/src/rest/mod.rs @@ -46,7 +46,7 @@ use hash_graph_store::{ data_type::DataTypeStore, entity::{DiffEntityParams, EntityStore}, entity_type::EntityTypeStore, - filter::{ParameterConversion, Selector}, + filter::{ParameterConversion, Selector, protection::PropertyProtectionFilterConfig}, pool::StorePool, property_type::PropertyTypeStore, subgraph::{ @@ -434,6 +434,7 @@ where pub query_logger: Option, pub api_config: ApiConfig, pub compiler: Arc, + pub filter_protection: Arc>, } /// A [`Router`] that only serves the `OpenAPI` specification (JSON, and necessary subschemas) for @@ -481,7 +482,8 @@ where .layer(Extension(dependencies.temporal_client)) .layer(Extension(dependencies.domain_regex)) .layer(Extension(dependencies.api_config)) - .layer(Extension(dependencies.compiler)); + .layer(Extension(dependencies.compiler)) + .layer(Extension(dependencies.filter_protection)); if let Some(query_logger) = dependencies.query_logger { router = router.layer(Extension(query_logger)); diff --git a/libs/@local/graph/postgres-store/src/store/postgres/mod.rs b/libs/@local/graph/postgres-store/src/store/postgres/mod.rs index cb1ee1e29bc..aacab09cfd8 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/mod.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/mod.rs @@ -93,7 +93,7 @@ pub struct PostgresStoreSettings { /// /// When set, filters on protected properties will automatically exclude /// specified entity types to prevent enumeration attacks. - pub filter_protection: PropertyProtectionFilterConfig<'static>, + pub filter_protection: Arc>, } impl Default for PostgresStoreSettings { @@ -101,7 +101,7 @@ impl Default for PostgresStoreSettings { Self { validate_links: true, skip_embedding_creation: false, - filter_protection: PropertyProtectionFilterConfig::hash_default(), + filter_protection: Arc::new(PropertyProtectionFilterConfig::hash_default()), } } } diff --git a/tests/graph/integration/postgres/email_filter_protection.rs b/tests/graph/integration/postgres/email_filter_protection.rs index d3f5a795fa7..8a5dbd65494 100644 --- a/tests/graph/integration/postgres/email_filter_protection.rs +++ b/tests/graph/integration/postgres/email_filter_protection.rs @@ -17,7 +17,7 @@ //! //! The tests below verify each case from the truth tables in the protection module. -use alloc::borrow::Cow; +use alloc::{borrow::Cow, sync::Arc}; use std::collections::HashSet; use hash_graph_postgres_store::store::PostgresStoreSettings; @@ -1416,7 +1416,7 @@ fn phone_filter(phone: &str) -> Filter<'static, type_system::knowledge::entity:: /// Creates a `FilterProtectionConfig` that protects both email AND phone for User. /// /// Excludes User entities when filtering by email or phone, UNLESS the actor is that User. -fn multi_property_config() -> PropertyProtectionFilterConfig<'static> { +fn multi_property_config() -> Arc> { let email_url = BaseUrl::new(EMAIL_PROPERTY_BASE_URL.to_owned()).expect("valid email base URL"); let phone_url = BaseUrl::new(PHONE_PROPERTY_BASE_URL.to_owned()).expect("valid phone base URL"); @@ -1443,7 +1443,7 @@ fn multi_property_config() -> PropertyProtectionFilterConfig<'static> { let mut config = PropertyProtectionFilterConfig::new(); config.protect_property(email_url, user_protection()); config.protect_property(phone_url, user_protection()); - config + Arc::new(config) } /// Seeds the database with multi-property protection config. @@ -2036,7 +2036,7 @@ fn secret_code_filter( /// Creates a `FilterProtectionConfig` for multi-type testing: /// - email protected for `User` (unless actor is that `User`) /// - `secret_code` protected for `SecretEntity` (unless actor is that `SecretEntity`) -fn multi_type_config() -> PropertyProtectionFilterConfig<'static> { +fn multi_type_config() -> Arc> { let email_url = BaseUrl::new(EMAIL_PROPERTY_BASE_URL.to_owned()).expect("valid email base URL"); let secret_code_url = BaseUrl::new(SECRET_CODE_PROPERTY_BASE_URL.to_owned()).expect("valid secret_code base URL"); @@ -2080,7 +2080,8 @@ fn multi_type_config() -> PropertyProtectionFilterConfig<'static> { ), ]), ); - config + + Arc::new(config) } /// Seeds the database with multi-type protection config. From cd24aca7df03e49ba8da2f127be07b89caab5908 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:04:22 +0200 Subject: [PATCH 26/34] chore: remove stray file --- libs/@local/hashql/eval/AUTHORIZATION.md | 476 ----------------------- 1 file changed, 476 deletions(-) delete mode 100644 libs/@local/hashql/eval/AUTHORIZATION.md diff --git a/libs/@local/hashql/eval/AUTHORIZATION.md b/libs/@local/hashql/eval/AUTHORIZATION.md deleted file mode 100644 index 94344931a5d..00000000000 --- a/libs/@local/hashql/eval/AUTHORIZATION.md +++ /dev/null @@ -1,476 +0,0 @@ -# Authorization patching for compiled queries - -Design spec for applying actor-specific authorization to actor-agnostic compiled -queries. Patching happens before the orchestrator receives the artifacts; the -execution engine stays oblivious to authorization decisions. - -## Invariant - -Compilation is actor-agnostic. The compiler produces the same `PreparedQueries` -regardless of who asks. Authorization is a runtime concern: the policy decision -matrix arrives per request, and the compiled artifacts are cloned and patched -before entering the orchestrator. - -The orchestrator does not change. It receives already-patched artifacts and -executes them as it always has. - -## Flow - -``` -Compile - | - v -PreparedQueries (shared, immutable, actor-agnostic) - | - | clone (per request) - v -PreparedQueries (owned) - | - | authorization::graft(&mut self, &PolicyComponents, &AuthorizationSettings) - v -PreparedQueries (patched: statement changes + auxiliary params) - | - v -Orchestrator::new(client, &patched_queries, context) - | - | fulfill_in: encode parameters + auxiliary_parameters, execute - v -Postgres -``` - -## Scope: parity with the current API - -The first implementation mirrors what `PolicyComponents` + -`Filter::for_policies` + `SelectCompiler::add_filter` do today in the old -`query_entities` path. Same policy decisions, clean compile/runtime split. - -## Two concerns, one graft - -The graft performs two independent modifications to each `PreparedQuery`: - -1. **Entity admission** (WHERE conditions): which rows survive. -2. **Property masking** (expression replacement): which property keys survive - within a row. - -Both are actor-specific, both modify the `SelectStatement`, and both belong -in the graft phase. - ---- - -## Entity admission - -### Condition shapes - -Entity UUID and web ID conditions are plain WHERE predicates on the base -table. IsOfType and IsOfBaseType use INNER JOINs on `entity_is_of_type_ids` -with `array_positions` for paired checks. CreatedByPrincipal checks -provenance on the joined `entity_editions`. - -INNER JOINs are safe here because both `entity_is_of_type_ids` and -`entity_editions` have total coverage: every entity in -`entity_temporal_metadata` has corresponding rows in both tables. This makes -INNER JOIN semantically equivalent to EXISTS in all Boolean contexts -(including under OR and NOT). - -| Policy condition | SQL shape | -| ------------------------- | ------------------------------------------------------------------------- | -| Entity UUID permit/forbid | `.entity_uuid = $N` or `= ANY($N::uuid[])` | -| Web ID permit/forbid | `.web_id = $N` or `= ANY($N::uuid[])` | -| CreatedByPrincipal | `ee.provenance->>'createdById' = $N::text` | -| IsOfType | `array_positions(eit.base_urls, $N) && array_positions(eit.versions, $M)` | -| IsOfBaseType | `$N = ANY(eit.base_urls)` | - -### Base table conditions - -Entity UUID and web ID reference columns on the base table -(`entity_temporal_metadata`). These are plain WHERE predicates: - -```sql --- single value -.entity_uuid = $N -.web_id = $N - --- batched (from OptimizationData) -.entity_uuid = ANY($N::uuid[]) -.web_id = ANY($N::uuid[]) -``` - -### CreatedByPrincipal - -Checks the entity's provenance on `entity_ids` (not `entity_editions`). -The existing `Filter::for_resource_filter` uses `EntityQueryPath::Provenance` -which maps to `Relation::EntityIds`. This is the entity-level provenance -(who created the entity originally), not the edition-level provenance -(who created each specific edition). - -The authorization projections join `entity_ids` if it is not already -joined by the compiled query (punch-through via `AuthorizationProjections`). -The join is on `(web_id, entity_uuid)`: - -```sql -ids.provenance->>'createdById' = $N::text -``` - -The actor UUID parameter is pushed as a UUID and cast to `text` in SQL -(via `.cast(PostgresType::Text)`), not converted to a string in Rust. -Anonymous actors use `ActorEntityUuid::public_actor()`, matching the -existing `for_resource_filter` behavior. - -### IsOfType (paired arrays) - -`entity_is_of_type_ids` stores `base_urls` (`text[]`) and `versions` -(`bigint[]`) as parallel arrays. Independent overlap on the two arrays loses -the pairing invariant (stored pairs `(A,1),(B,2)` would falsely match query -`(A,2)`). `array_positions` preserves pairing by finding overlapping -subscript positions: - -```sql -array_positions(eit.base_urls, $N_base::text) -&& array_positions(eit.versions, $N_version::bigint) -``` - -`array_positions(arr, val)` returns all subscripts where the element matches. -The `&&` operator checks whether the two integer arrays share any element. -A shared position means `base_urls[i] = $base AND versions[i] = $version`. - -The `entity_is_of_type_ids` table is joined via `AuthorizationProjections`, -which always allocates its own join (the compiled query's LATERAL aggregate -serves a different purpose). - -### IsOfBaseType - -Base URL alone is sufficient; no pairing needed: - -```sql -$N_base = ANY(eit.base_urls) -``` - -### Combination algebra - -Same as `Filter::for_policies`: - -``` -blank forbid -> WHERE FALSE (deny all, short circuit) -blank permit, no forbids -> no conditions (allow all) -blank permit + forbids -> WHERE NOT (f1 OR f2 OR ...) -permits only -> WHERE (p1 OR p2 OR ... OR uuid_batch OR web_batch) -permits + forbids -> WHERE (permits) AND NOT (forbids) -no permits -> WHERE FALSE (deny all) -``` - -### Two-phase allocation - -Auxiliary parameters must not be allocated speculatively. Every parameter -pushed must be referenced by an expression in the final SQL. Dead parameters -cause bind-arity mismatches at runtime. - -The graft uses a two-phase approach: - -1. **Normalize**: walk policies, collect constraint references and determine - the algebra (blank permit, blank forbid, constrained permits/forbids). - No SQL expressions or parameters are allocated. - -2. **Lower**: convert only the surviving constraints to SQL expressions and - push their parameter values. If blank_permit is true, only forbids are - lowered. If no permits survive, return FALSE with empty parameters. - -The optimization pass (`OptimizationData`) is also conditional: batched -permit UUIDs and web IDs are only lowered if permits are live (i.e. -`blank_permit` is false). - -### Instance admin bypass - -When `policy_components.is_instance_admin()` is true, no admission conditions -are added and no property masking is applied. The graft is a no-op. - ---- - -## Property masking - -### Compilation: entity_editions as a subquery - -`entity_editions` is always joined as a subquery with explicit projections, -regardless of whether masking will be applied. This is a general mechanism, not -authorization-specific: - -```sql -INNER JOIN ( - SELECT entity_edition_id, archived, confidence, provenance, - properties AS properties, - property_metadata AS property_metadata - FROM entity_editions -) AS ee ON ee.entity_edition_id = base.entity_edition_id -``` - -The `properties` and `property_metadata` projections are identity by default. -Each is a `SelectExpression::Expression` with a known alias. This gives any -runtime pass a stable injection point: scan the subquery's SELECT list, find -the entry by alias, replace the inner expression. - -If entity_editions is not joined (the query does not touch properties, -provenance, confidence, or other edition fields), the subquery does not exist -and there is nothing to mask. - -### Graft: expression replacement - -The graft locates the entity_editions subquery in the FROM tree by matching -the alias from `Projections::entity_editions`. It scans the subquery's SELECT -list for entries with alias `properties` and `property_metadata`, and replaces -their inner expressions: - -``` -Before: properties AS properties -After: properties - $N::text[] AS properties - -Before: property_metadata AS property_metadata -After: property_metadata - $N::text[] AS property_metadata -``` - -The replacement expression can be anything: a simple -`properties - $N::text[]` for unconditional masking, or a more complex -CASE WHEN expression for conditional per-type masking (matching the existing -`PropertyProtectionFilter` behavior that builds type-dependent mask arrays). -The injection mechanism is general; the graft builds whatever expression the -policy requires. - -The alias is unchanged, so all downstream references (`ee.properties`, -filter expressions using `ee.properties->>'path'`) transparently get the -masked version. - -### Scope of replacement expressions - -The replacement expression lives inside the entity_editions subquery. It can -reference: - -- Columns from the inner source alias (e.g. `ee_src.entity_edition_id`) -- Constants and parameter placeholders -- Correlated subqueries keyed on `ee_src.entity_edition_id` - -It cannot reference outer aliases (e.g. `entity_temporal_metadata`) unless -the subquery is made LATERAL. For type-dependent masks, use correlated -subqueries inside the replacement expression, not references to outer joins. - -### Composability - -This approach is general. The subquery form is always present when -entity_editions is joined. Any pass (authorization, future masking, etc.) can -scan the SELECT list and replace expressions. The mechanism is not coupled to -authorization. - ---- - -## Auxiliary parameters - -Authorization values (UUIDs, web IDs, mask keys, base URLs, version strings) -are runtime data with no relation to the compiler's parameter system -(`ParameterValue<'heap>`). They do not belong in `Parameters`. - -`PreparedQuery` carries a sidepiece: - -```rust -pub struct PreparedQuery<'heap, A: Allocator> { - pub vertex_type: VertexType, - pub parameters: Parameters<'heap, A>, - pub statement: SelectStatement, - pub columns: Vec, - pub join_metadata: JoinMetadata, - - /// Opaque parameter values appended after compiled parameters during - /// encoding. Added by the authorization graft. The orchestrator does - /// not interpret them. - /// - /// These are `'static` owned values (Uuid, String, Vec, etc.). - /// At encoding time the orchestrator chains borrowed references to - /// these after the compiled parameters. They are not moved into the - /// compiled parameter vector (which uses a different allocator and - /// lifetime). - pub auxiliary_parameters: Vec>, -} -``` - -### Index discipline - -Compiled parameters occupy `$1..$K` (where `K = parameters.len()`). -Auxiliary parameters occupy `$(K+1)..$(K+M)`. Expressions generated by the -graft use absolute indices starting from `K+1`. - -The graft is responsible for index correctness: each `Expression::Parameter(n)` -it emits must have a corresponding entry in `auxiliary_parameters` at position -`n - K - 1`. A mismatch is a bug in the graft, not a user error. - -### Encoding - -The orchestrator chains borrowed references: compiled params first, then -auxiliary. The auxiliary values stay in their `Vec>` -and are not moved into the compiled parameter vector (different allocator, -different lifetime): - -```rust -// Compiled parameters (existing) -let mut encoded = Vec::with_capacity_in(query.parameters.len(), alloc.clone()); -for param in query.parameters.iter() { - encoded.push(encode_parameter_in(param, ...)?); -} - -// Chain borrowed references: compiled, then auxiliary -let compiled = encoded.iter().map(|p| &**p as &(dyn ToSql + Sync)); -let auxiliary = query.auxiliary_parameters.iter().map(|p| &**p as &(dyn ToSql + Sync)); - -let response = client - .query_raw(&statement, compiled.chain(auxiliary)) - .await?; -``` - -The orchestrator does not know what auxiliary parameters mean. It just -chains them. - ---- - -## Construction: PolicyComponents to SQL - -The `authorization` module provides: - -```rust -/// Grafts authorization conditions and property masking onto compiled -/// queries. -/// -/// The caller is responsible for cloning `PreparedQueries` before calling -/// this. The function mutates the clone in place. -pub fn graft( - queries: &mut PreparedQueries<'_, impl Allocator>, - policy: &PolicyComponents, - settings: &AuthorizationSettings, -) -``` - -`AuthorizationSettings` carries the filter protection config (which property -keys to mask, per entity type or globally). - -### Internal steps - -1. **Pre-analyze** the policy (once per graft, reused across queries): - - Iterate `policy.extract_filter_policies(ActionName::ViewEntity)` - - Separate permits and forbids - - Collect optimizable UUID/web batches (mirrors `OptimizationData`) - - Normalize actor ID for provenance - -2. **Per query**, build admission conditions: - - Read `param_offset = query.parameters.len()` - - Lower each policy decision to `Expression` nodes referencing `$N` - from the offset - - Collect corresponding `Box` values - - Combine with the permit/forbid algebra - - Push into `statement.where_expression` - - Store values in `auxiliary_parameters` - -3. **Per query**, apply property mask (if entity_editions is joined): - - Locate the entity_editions subquery via `join_metadata` - - Scan its SELECT list for `properties` / `property_metadata` aliases - - Build the mask expression (simple `column - $N::text[]` for - unconditional, or CASE WHEN for conditional per-type masking) - - Replace the inner expressions - - Add auxiliary parameters for any runtime values in the mask - ---- - -## JoinMetadata - -Captured from `Projections` after compilation: - -```rust -pub struct JoinMetadata { - /// Base table alias (entity_temporal_metadata). Always present. - pub base_alias: Alias, - /// entity_editions alias, if the query joins it. The graft uses this - /// to locate the entity_editions subquery for property masking. - pub entity_editions: Option, -} -``` - -`Projections` exposes a `snapshot()` method: - -```rust -impl Projections { - pub fn snapshot(&self) -> JoinMetadata { - JoinMetadata { - base_alias: self.base_alias, - entity_editions: self.entity_editions, - } - } -} -``` - ---- - -## Changes to existing code - -### `PostgresCompiler` - -Remove `property_mask: Option` and the `with_property_mask` builder -method. The mask moves entirely to the graft phase. - -Remove the compile-time masking logic in `compile_graph_read_entity` that wraps -property expressions with `Expression::subtract`. - -### `Projections::build_entity_editions` - -Change from joining the raw `entity_editions` table to joining a subquery with -explicit column projections. The `properties` and `property_metadata` columns -are projected as identity expressions with named aliases. - -### `PreparedQuery` - -Add `join_metadata: JoinMetadata` and `auxiliary_parameters: Vec>`fields. Initialize`auxiliary_parameters` to empty at compile time. - -### Orchestrator encoding - -Append `query.auxiliary_parameters` after compiled parameters in the encoding -loop. - ---- - -## File layout - -``` -libs/@local/hashql/eval/src/postgres/ - authorization.rs -- graft(), AuthorizationSettings, - policy analysis, condition builders - mod.rs -- PreparedQuery gains join_metadata and - auxiliary_parameters; remove - property_mask from PostgresCompiler - projections.rs -- Projections::snapshot() -> JoinMetadata; - build_entity_editions produces subquery - parameters.rs -- unchanged - -libs/@local/hashql/eval/src/orchestrator/ - request/graph_read.rs -- encoding loop appends auxiliary_parameters - mod.rs -- unchanged -``` - ---- - -## Cloning - -`Box` is not cloneable, so `PreparedQuery` cannot derive -`Clone`. Provide a `clone_for_graft()` method that clones the actor-agnostic -fields (`statement`, `parameters`, `columns`, `join_metadata`, `vertex_type`) -and initializes `auxiliary_parameters` to empty. This is the intended clone -path: callers clone the shared artifact, then graft onto the clone. - -Do not silently clone a grafted query in a way that drops auxiliary -parameters. - ---- - -## Not in scope - -- Direct-type admission semantics from auth.md. This spec provides the - grafting mechanism; the admission semantics produce the conditions. -- Property-level masking via MIR labels (the information-flow pass from - auth.md). Future work on top of this mechanism. -- Filter protection / enumeration attack mitigation beyond property masking. -- Multi-backend authorization. -- Reusing the compiled LATERAL aggregate for type filtering (structurally - different purpose). From 4e127417eaee34e0d9dda1d41294c88ca1d7e231 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:21:34 +0200 Subject: [PATCH 27/34] chore: add `Arc` where needed --- tests/graph/benches/graph/scenario/runner.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/graph/benches/graph/scenario/runner.rs b/tests/graph/benches/graph/scenario/runner.rs index b084b6524d0..70ddc59a63b 100644 --- a/tests/graph/benches/graph/scenario/runner.rs +++ b/tests/graph/benches/graph/scenario/runner.rs @@ -1,4 +1,5 @@ extern crate alloc; +use alloc::sync::Arc; use core::sync::atomic::{self, AtomicUsize}; use std::{collections::HashMap, fs::File, io::BufReader, path::Path}; @@ -216,7 +217,7 @@ impl Runner { PostgresStoreSettings { validate_links: true, skip_embedding_creation: true, - filter_protection: PropertyProtectionFilterConfig::new(), // Disabled for benchmarks + filter_protection: Arc::new(PropertyProtectionFilterConfig::new()), // Disabled for benchmarks }, ) .await From 96a73c4b65234d38109a302380685d6af9e66dff Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:31:21 +0200 Subject: [PATCH 28/34] chore: fix debug assertions --- libs/@local/hashql/eval/src/postgres/authorization/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index ca9d843fbae..df2fc0d1157 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -214,13 +214,15 @@ impl PatchPreparedQueryLayer // result, and not to one of its parts. *expression = Expression::subtract(base, keys_to_remove.clone()).grouped(); - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { grafted_columns += 1; } } } - if cfg!(debug_assertions) { + #[cfg(debug_assertions)] + { debug_assert_eq!( grafted_columns, 2, "entity_editions LATERAL must contain both `properties` and `property_metadata` \ From ef5732df8d70a79982e994770d3acd69ecb52c77 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:47:57 +0200 Subject: [PATCH 29/34] fix: turborepo --- libs/@local/hashql/eval/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/@local/hashql/eval/package.json b/libs/@local/hashql/eval/package.json index 76ec76b7fbe..7a077119f5a 100644 --- a/libs/@local/hashql/eval/package.json +++ b/libs/@local/hashql/eval/package.json @@ -10,17 +10,17 @@ "test:unit": "mise run test:unit @rust/hashql-eval" }, "dependencies": { + "@blockprotocol/type-system-rs": "workspace:*", + "@rust/hash-graph-authorization": "workspace:*", "@rust/hash-graph-postgres-store": "workspace:*", + "@rust/hash-graph-store": "workspace:*", "@rust/hashql-core": "workspace:*", "@rust/hashql-diagnostics": "workspace:*", "@rust/hashql-hir": "workspace:*", "@rust/hashql-mir": "workspace:*" }, "devDependencies": { - "@blockprotocol/type-system-rs": "workspace:*", "@rust/error-stack": "workspace:*", - "@rust/hash-graph-authorization": "workspace:*", - "@rust/hash-graph-store": "workspace:*", "@rust/hash-graph-test-data": "workspace:*" } } From 3307b1affa788035b649183b4dcbff31fd1821c6 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:50:01 +0200 Subject: [PATCH 30/34] fix: openapi --- libs/@local/graph/api/openapi/openapi.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libs/@local/graph/api/openapi/openapi.json b/libs/@local/graph/api/openapi/openapi.json index 85015cfa552..474a050b902 100644 --- a/libs/@local/graph/api/openapi/openapi.json +++ b/libs/@local/graph/api/openapi/openapi.json @@ -2471,6 +2471,15 @@ ], "operationId": "query_hashql", "parameters": [ + { + "name": "X-Authenticated-User-Actor-Id", + "in": "header", + "description": "The ID of the actor which is used to authorize the request", + "required": true, + "schema": { + "$ref": "#/components/schemas/ActorEntityUuid" + } + }, { "name": "Interactive", "in": "header", From e0e455aab666d7fa2fe319f6f24bc838fe6d7c2a Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:13:41 +0200 Subject: [PATCH 31/34] fix: docs --- .../@local/hashql/eval/src/postgres/prepared.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/libs/@local/hashql/eval/src/postgres/prepared.rs b/libs/@local/hashql/eval/src/postgres/prepared.rs index 94c4821c542..d752604e78b 100644 --- a/libs/@local/hashql/eval/src/postgres/prepared.rs +++ b/libs/@local/hashql/eval/src/postgres/prepared.rs @@ -76,9 +76,9 @@ pub struct PatchContext<'ctx, A: Allocator> { /// A composed patch chain that can be applied to a [`PreparedQuery`]. /// -/// Implemented by [`HNil`] (which materializes joins) and by -/// [`HCons`] (which delegates to its head layer, passing the tail -/// as the `next` continuation). +/// The terminal element materializes all auxiliary joins; composite +/// elements delegate to their head layer, passing the remaining +/// chain as the `next` continuation. pub trait PatchPreparedQuery { fn patch_query( &self, @@ -182,11 +182,11 @@ where /// Builder for a [`PatchPreparedQueryLayer`] pipeline. /// /// Layers are added with [`layer`](Self::layer) and execute outermost-first: -/// the last layer added runs first. The pipeline terminates at [`HNil`], -/// which materializes all auxiliary joins accumulated by the layers. +/// the last layer added runs first. The pipeline terminates by +/// materializing all auxiliary joins accumulated by the layers. /// /// ```text -/// PreparedQueryPatch::new() // HNil (join materialization) +/// PreparedQueryPatch::new() /// .layer(authorization) // runs first, calls next for joins /// .apply(&mut query, scratch); /// ``` @@ -216,9 +216,8 @@ impl PreparedQueryPatch { /// Runs the full patch pipeline against `query`. /// - /// Constructs [`AuxiliaryProjections`] from the query's compiled - /// projections, then invokes the layer chain. The terminal [`HNil`] - /// materializes all registered auxiliary joins into the FROM clause. + /// May modify the query's statement and add auxiliary parameters + /// accessible through [`PreparedQuery::auxiliary_parameters`]. pub fn apply(self, query: &mut PreparedQuery, scratch: S) where T: PatchPreparedQuery, From bc78a3b0ec792d0d168f5b88924b20948a95fe9f Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:47:40 +0200 Subject: [PATCH 32/34] fix: merge conflicts --- .../eval/src/postgres/authorization/mod.rs | 2 +- .../src/postgres/authorization/policy/mod.rs | 10 +++---- .../postgres/authorization/policy/tests.rs | 4 +-- .../postgres/authorization/protection/mod.rs | 6 ++--- .../eval/src/postgres/authorization/tests.rs | 4 +-- .../hashql/eval/src/postgres/projections.rs | 26 +++++++++---------- .../patch_blank_forbid_denies_all.snap | 8 +++--- .../patch_protection_with_type_base_urls.snap | 8 +++--- .../patch_with_policy_and_protection.snap | 8 +++--- .../policy/algebra_permits_and_forbids.snap | 2 +- .../constraint_any_with_type_filter.snap | 2 +- .../policy/filter_all_conjunction.snap | 2 +- .../policy/filter_any_disjunction.snap | 2 +- .../policy/is_of_base_type_any.snap | 2 +- .../policy/is_of_type_overlap.snap | 2 +- .../build_joins_inserts_before_laterals.snap | 6 ++--- .../build_joins_no_laterals_with_auth.snap | 6 ++--- .../lower_filter_any_disjunction.snap | 2 +- .../protection/lower_filter_in.snap | 2 +- .../protection/lower_filter_nested_all.snap | 2 +- .../resolve_path_type_base_urls.snap | 2 +- .../protection/transpile_hash_default.snap | 2 +- 22 files changed, 55 insertions(+), 55 deletions(-) diff --git a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs index df2fc0d1157..ddb181bbdb6 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/mod.rs @@ -149,7 +149,7 @@ impl PatchPreparedQueryLayer query.statement.where_expression.add_condition(condition); // Lower protection BEFORE join materialization so its join demands - // (e.g. entity_is_of_type_ids for TypeBaseUrls) are registered. + // (e.g. entity_edition_cache for TypeBaseUrls) are registered. // The resulting mask expression is grafted AFTER joins are built. let entity_edition_alias = context.projections.entity_edition_alias(); diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs index e4091e91538..74297bf76ee 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/mod.rs @@ -35,7 +35,7 @@ fn convert_is_of_type( unit: &mut PolicyTranslationUnit<'_, A>, url: VersionedUrl, ) -> Expression { - let table = unit.projections.entity_is_of_type_ids(); + let table = unit.projections.entity_edition_cache(); let base_url_index = unit.parameters.push(url.base_url); let version_index = unit.parameters.push(url.version); @@ -45,7 +45,7 @@ fn convert_is_of_type( hash_graph_postgres_store::store::postgres::query::Function::ArrayPositions( Box::new(Expression::ColumnReference(ColumnReference { correlation: Some(table.clone()), - name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::BaseUrls).into(), + name: Column::EntityEditionCache(table::EntityEditionCache::BaseUrls).into(), })), Box::new(Expression::Parameter(base_url_index)), ), @@ -56,7 +56,7 @@ fn convert_is_of_type( hash_graph_postgres_store::store::postgres::query::Function::ArrayPositions( Box::new(Expression::ColumnReference(ColumnReference { correlation: Some(table), - name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::Versions).into(), + name: Column::EntityEditionCache(table::EntityEditionCache::Versions).into(), })), Box::new(Expression::Parameter(version_index)), ), @@ -86,8 +86,8 @@ fn convert_is_of_base_type( op: BinaryOperator::In, left: Box::new(Expression::Parameter(base_url_index)), right: Box::new(Expression::ColumnReference(ColumnReference { - correlation: Some(unit.projections.entity_is_of_type_ids()), - name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::BaseUrls).into(), + correlation: Some(unit.projections.entity_edition_cache()), + name: Column::EntityEditionCache(table::EntityEditionCache::BaseUrls).into(), })), }) } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs index 639f335decb..71d52fab5d1 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs @@ -403,8 +403,8 @@ fn algebra_blank_forbid_denies_all() { "blank forbid should not register entity_ids", ); assert!( - fixture.projections.entity_is_of_type_ids.is_none(), - "blank forbid should not register entity_is_of_type_ids", + fixture.projections.entity_edition_cache.is_none(), + "blank forbid should not register entity_edition_cache", ); } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/protection/mod.rs b/libs/@local/hashql/eval/src/postgres/authorization/protection/mod.rs index b1e5f26e54a..f93cf368f37 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/protection/mod.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/protection/mod.rs @@ -32,11 +32,11 @@ fn resolve_path( correlation: Some(unit.projections.temporal_metadata()), name: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EntityUuid).into(), }), - // eit.base_urls + // eec.base_urls PropertyFilterEntityQueryPath::TypeBaseUrls => { Expression::ColumnReference(ColumnReference { - correlation: Some(unit.projections.entity_is_of_type_ids()), - name: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::BaseUrls).into(), + correlation: Some(unit.projections.entity_edition_cache()), + name: Column::EntityEditionCache(table::EntityEditionCache::BaseUrls).into(), }) } } diff --git a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs index 927ec31d42b..74eabb5e110 100644 --- a/libs/@local/hashql/eval/src/postgres/authorization/tests.rs +++ b/libs/@local/hashql/eval/src/postgres/authorization/tests.rs @@ -661,7 +661,7 @@ fn patch_instance_admin_bypasses_protection() { } /// Protection filter that references `TypeBaseUrls`, requiring the -/// `entity_is_of_type_ids` auxiliary join to be in scope inside the +/// `entity_edition_cache` auxiliary join to be in scope inside the /// `entity_editions` LATERAL mask expression. #[test] fn patch_protection_with_type_base_urls() { @@ -688,7 +688,7 @@ fn patch_protection_with_type_base_urls() { let actor = Some(ActorId::User(UserId::new(ACTOR_UUID))); let policy = policy_components(actor, vec![permit(|| None)]); - // Protection uses TypeBaseUrls path, which demands entity_is_of_type_ids join. + // Protection uses TypeBaseUrls path, which demands entity_edition_cache join. let mut properties = PropertyProtectionFilterConfig::new(); properties.protect_property( BaseUrl::new("https://hash.ai/@h/types/property-type/email/".to_owned()) diff --git a/libs/@local/hashql/eval/src/postgres/projections.rs b/libs/@local/hashql/eval/src/postgres/projections.rs index 673bd60447b..cb2d94b5a62 100644 --- a/libs/@local/hashql/eval/src/postgres/projections.rs +++ b/libs/@local/hashql/eval/src/postgres/projections.rs @@ -513,7 +513,7 @@ pub struct AuxiliaryProjections { base: Projections, pub entity_ids: Option, - pub entity_is_of_type_ids: Option, + pub entity_edition_cache: Option, } impl AuxiliaryProjections { @@ -522,7 +522,7 @@ impl AuxiliaryProjections { index: base.index, base: base.snapshot(), entity_ids: None, - entity_is_of_type_ids: None, + entity_edition_cache: None, } } @@ -574,18 +574,18 @@ impl AuxiliaryProjections { /// /// Always allocates a fresh join; the base projections' type aggregate /// is a scoped LATERAL subquery and cannot be reused. - pub(crate) fn entity_is_of_type_ids(&mut self) -> TableReference<'static> { - let alias = if let Some(alias) = self.entity_is_of_type_ids { + pub(crate) fn entity_edition_cache(&mut self) -> TableReference<'static> { + let alias = if let Some(alias) = self.entity_edition_cache { alias } else { let alias = self.next_alias(); - self.entity_is_of_type_ids = Some(alias); + self.entity_edition_cache = Some(alias); alias }; TableReference { schema: None, - name: TableName::from(Table::EntityIsOfTypeIds), + name: TableName::from(Table::EntityEditionCache), alias: Some(alias), } } @@ -615,7 +615,7 @@ impl AuxiliaryProjections { tablesample: None, }; - if self.entity_ids.is_none() && self.entity_is_of_type_ids.is_none() { + if self.entity_ids.is_none() && self.entity_edition_cache.is_none() { return from; } @@ -633,18 +633,18 @@ impl AuxiliaryProjections { core = self.base.build_entity_ids(core, alias); } - if let Some(alias) = self.entity_is_of_type_ids { + if let Some(alias) = self.entity_edition_cache { let fk = ForeignKeyReference::Single { on: Column::EntityTemporalMetadata(table::EntityTemporalMetadata::EditionId), - join: Column::EntityIsOfTypeIds(table::EntityIsOfTypeIds::EntityEditionId), + join: Column::EntityEditionCache(table::EntityEditionCache::EntityEditionId), join_type: JoinType::Inner, }; core = core .join( JoinType::Inner, - FromItem::table(Table::EntityIsOfTypeIds) - .alias(Table::EntityIsOfTypeIds.aliased(alias)), + FromItem::table(Table::EntityEditionCache) + .alias(Table::EntityEditionCache.aliased(alias)), ) .on(fk.conditions(self.base.base_alias, alias)) .build(); @@ -793,7 +793,7 @@ mod tests { fn build_joins_inserts_before_laterals() { let base = Projections::new(); let mut aux = AuxiliaryProjections::new(&base); - aux.entity_is_of_type_ids(); + aux.entity_edition_cache(); let core = FromItem::table(Table::EntityTemporalMetadata) .alias(TableReference { @@ -823,7 +823,7 @@ mod tests { let base = Projections::new(); let mut aux = AuxiliaryProjections::new(&base); aux.entity_ids(); - aux.entity_is_of_type_ids(); + aux.entity_edition_cache(); let from = FromItem::table(Table::EntityTemporalMetadata) .alias(TableReference { diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap index c189dea705c..86559089d2e 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_blank_forbid_denies_all.snap @@ -24,9 +24,9 @@ SELECT ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", ("continuation_0_0"."row")."values" AS "continuation_0_0_values" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" -INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" +INNER JOIN "entity_edition_cache" AS "entity_edition_cache_0_0_2" ON - "entity_is_of_type_ids_0_0_2"."entity_edition_id" + "entity_edition_cache_0_0_2"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" CROSS JOIN LATERAL ( SELECT @@ -36,7 +36,7 @@ CROSS JOIN LATERAL ( - ( CASE WHEN - ($5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls")) + ($5 = ANY("entity_edition_cache_0_0_2"."base_urls")) AND ( "entity_temporal_metadata_0_0_0"."entity_uuid" != $6 ) @@ -53,7 +53,7 @@ CROSS JOIN LATERAL ( - ( CASE WHEN - ($5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls")) + ($5 = ANY("entity_edition_cache_0_0_2"."base_urls")) AND ( "entity_temporal_metadata_0_0_0"."entity_uuid" != $6 ) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap index b96a0d47112..789fc585325 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_protection_with_type_base_urls.snap @@ -24,9 +24,9 @@ SELECT ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", ("continuation_0_0"."row")."values" AS "continuation_0_0_values" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" -INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" +INNER JOIN "entity_edition_cache" AS "entity_edition_cache_0_0_2" ON - "entity_is_of_type_ids_0_0_2"."entity_edition_id" + "entity_edition_cache_0_0_2"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" CROSS JOIN LATERAL ( SELECT @@ -36,7 +36,7 @@ CROSS JOIN LATERAL ( - ( CASE WHEN - $5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls") + $5 = ANY("entity_edition_cache_0_0_2"."base_urls") THEN ARRAY[$6]::text [] ELSE ARRAY[]::text [] END @@ -50,7 +50,7 @@ CROSS JOIN LATERAL ( - ( CASE WHEN - $5 = ANY("entity_is_of_type_ids_0_0_2"."base_urls") + $5 = ANY("entity_edition_cache_0_0_2"."base_urls") THEN ARRAY[$6]::text [] ELSE ARRAY[]::text [] END diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap index 32d5da77d1a..a67704e7712 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/integration/patch_with_policy_and_protection.snap @@ -24,9 +24,9 @@ SELECT ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", ("continuation_0_0"."row")."values" AS "continuation_0_0_values" FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" -INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" +INNER JOIN "entity_edition_cache" AS "entity_edition_cache_0_0_2" ON - "entity_is_of_type_ids_0_0_2"."entity_edition_id" + "entity_edition_cache_0_0_2"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" CROSS JOIN LATERAL ( SELECT @@ -91,8 +91,8 @@ WHERE AND ( NOT ( ( - ARRAY_POSITIONS("entity_is_of_type_ids_0_0_2"."base_urls", $7) - && ARRAY_POSITIONS("entity_is_of_type_ids_0_0_2"."versions", $8) + ARRAY_POSITIONS("entity_edition_cache_0_0_2"."base_urls", $7) + && ARRAY_POSITIONS("entity_edition_cache_0_0_2"."versions", $8) ) ) ) diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap index 670002b08f9..b24727f97b3 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/algebra_permits_and_forbids.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "[ResolvedPolicy { original_policy_id: PolicyId(bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb), effect: Permit, actions: [ViewEntity], resource: Some(Entity(Exact { id: EntityUuid(11111111-1111-1111-1111-111111111111) })) }, ResolvedPolicy { original_policy_id: PolicyId(cccccccc-cccc-cccc-cccc-cccccccccccc), effect: Forbid, actions: [ViewEntity], resource: Some(Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })) }]" expression: "snapshot_with_params(&result.condition.transpile_to_string(),\n&fixture.parameters)" --- -(("entity_temporal_metadata_0_0_0"."entity_uuid" = $1)) AND (NOT((array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $2) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $3)))) +(("entity_temporal_metadata_0_0_0"."entity_uuid" = $1)) AND (NOT((array_positions("entity_edition_cache_0_0_1"."base_urls", $2) && array_positions("entity_edition_cache_0_0_1"."versions", $3)))) parameters: AuxiliaryParameters { initial_offset: 0, parameters: [EntityUuid(11111111-1111-1111-1111-111111111111), "https://hash.ai/@h/types/entity-type/user/", OntologyTypeVersion { major: 1, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap index 72ae9dd5d97..8f3a417c77a 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/constraint_any_with_type_filter.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Entity(Any { filter: IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } } })" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2) +array_positions("entity_edition_cache_0_0_1"."base_urls", $1) && array_positions("entity_edition_cache_0_0_1"."versions", $2) parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", OntologyTypeVersion { major: 1, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap index 7cc3ddb7c8a..a00a83a6bd8 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_all_conjunction.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "All { filters: [CreatedByPrincipal, IsOfBaseType { entity_type: \"https://hash.ai/@h/types/entity-type/user/\" }] }" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -("entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text)) AND ($2 = ANY("entity_is_of_type_ids_0_0_2"."base_urls")) +("entity_ids_0_0_1"."provenance"->>'createdById' = ($1::text)) AND ($2 = ANY("entity_edition_cache_0_0_2"."base_urls")) parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/entity-type/user/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap index fd4d977d7b3..eebdfa883b8 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/filter_any_disjunction.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "Any { filters: [IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/user/\", version: OntologyTypeVersion { major: 1, pre_release: None } } }, IsOfType { entity_type: VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/machine/\", version: OntologyTypeVersion { major: 2, pre_release: None } } }] }" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -((array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2)) OR (array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $3) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $4))) +((array_positions("entity_edition_cache_0_0_1"."base_urls", $1) && array_positions("entity_edition_cache_0_0_1"."versions", $2)) OR (array_positions("entity_edition_cache_0_0_1"."base_urls", $3) && array_positions("entity_edition_cache_0_0_1"."versions", $4))) parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", OntologyTypeVersion { major: 1, pre_release: None }, "https://hash.ai/@h/types/entity-type/machine/", OntologyTypeVersion { major: 2, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap index 0243095a1d1..a06819e9a0d 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_base_type_any.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "\"https://hash.ai/@h/types/entity-type/machine/\"" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -$1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls") +$1 = ANY("entity_edition_cache_0_0_1"."base_urls") parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/machine/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap index 2a4b5aafb75..be7b4936932 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/policy/is_of_type_overlap.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/policy/tests.rs description: "VersionedUrl { base_url: \"https://hash.ai/@h/types/entity-type/machine/\", version: OntologyTypeVersion { major: 1, pre_release: None } }" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -array_positions("entity_is_of_type_ids_0_0_1"."base_urls", $1) && array_positions("entity_is_of_type_ids_0_0_1"."versions", $2) +array_positions("entity_edition_cache_0_0_1"."base_urls", $1) && array_positions("entity_edition_cache_0_0_1"."versions", $2) parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/machine/", OntologyTypeVersion { major: 1, pre_release: None }] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap index 2617daa18b8..2e4773da3f3 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_inserts_before_laterals.snap @@ -1,11 +1,11 @@ --- source: libs/@local/hashql/eval/src/postgres/projections.rs -description: "AuxiliaryProjections { index: 2, base: Projections { index: 1, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: None, entity_ids: None, entity_type_ids: None, left: None, right: None }, entity_ids: None, entity_is_of_type_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }) }, 2 continuation laterals" +description: "AuxiliaryProjections { index: 2, base: Projections { index: 1, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: None, entity_ids: None, entity_type_ids: None, left: None, right: None }, entity_ids: None, entity_edition_cache: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }) }, 2 continuation laterals" expression: result.transpile_to_string() --- "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" -INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_1" - ON "entity_is_of_type_ids_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" +INNER JOIN "entity_edition_cache" AS "entity_edition_cache_0_0_1" + ON "entity_edition_cache_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" CROSS JOIN LATERAL (SELECT FROM "entity_temporal_metadata" AS "continuation_1") AS "continuation_1" CROSS JOIN LATERAL (SELECT diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap index 0917dc93750..3e10be55d53 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/projections/build_joins_no_laterals_with_auth.snap @@ -1,11 +1,11 @@ --- source: libs/@local/hashql/eval/src/postgres/projections.rs -description: "AuxiliaryProjections { index: 3, base: Projections { index: 1, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: None, entity_ids: None, entity_type_ids: None, left: None, right: None }, entity_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }), entity_is_of_type_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 2 }) }" +description: "AuxiliaryProjections { index: 3, base: Projections { index: 1, base_alias: Alias { condition_index: 0, chain_depth: 0, number: 0 }, entity_editions: None, entity_ids: None, entity_type_ids: None, left: None, right: None }, entity_ids: Some(Alias { condition_index: 0, chain_depth: 0, number: 1 }), entity_edition_cache: Some(Alias { condition_index: 0, chain_depth: 0, number: 2 }) }" expression: result.transpile_to_string() --- "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0" INNER JOIN "entity_ids" AS "entity_ids_0_0_1" ON "entity_ids_0_0_1"."web_id" = "entity_temporal_metadata_0_0_0"."web_id" AND "entity_ids_0_0_1"."entity_uuid" = "entity_temporal_metadata_0_0_0"."entity_uuid" -INNER JOIN "entity_is_of_type_ids" AS "entity_is_of_type_ids_0_0_2" - ON "entity_is_of_type_ids_0_0_2"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" +INNER JOIN "entity_edition_cache" AS "entity_edition_cache_0_0_2" + ON "entity_edition_cache_0_0_2"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id" diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap index 0b006b35fec..bb94a356c73 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_any_disjunction.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "Any([Equal(Path { path: Uuid }, ActorId), In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls })])" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -(("entity_temporal_metadata_0_0_0"."entity_uuid" = $1) OR ($2 = ANY("entity_is_of_type_ids_0_0_1"."base_urls"))) +(("entity_temporal_metadata_0_0_0"."entity_uuid" = $1) OR ($2 = ANY("entity_edition_cache_0_0_1"."base_urls"))) parameters: AuxiliaryParameters { initial_offset: 0, parameters: [ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/entity-type/user/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap index 7dacc792c0f..9914b7df3a8 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_in.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls })" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -$1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls") +$1 = ANY("entity_edition_cache_0_0_1"."base_urls") parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/"] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap index 7948cc8a267..49715021218 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/lower_filter_nested_all.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "All([In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls }), NotEqual(Path { path: Uuid }, ActorId)])" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -($1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) +($1 = ANY("entity_edition_cache_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa))] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap index c466108c35e..0af762fd121 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/resolve_path_type_base_urls.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: TypeBaseUrls expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -"entity_is_of_type_ids_0_0_1"."base_urls" +"entity_edition_cache_0_0_1"."base_urls" parameters: AuxiliaryParameters { initial_offset: 0, parameters: [] } diff --git a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap index c515bc2280d..781e2410db0 100644 --- a/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap +++ b/libs/@local/hashql/eval/tests/ui/postgres/authorization/protection/transpile_hash_default.snap @@ -3,6 +3,6 @@ source: libs/@local/hashql/eval/src/postgres/authorization/protection/tests.rs description: "PropertyProtectionFilterConfig { property_filters: {\"https://hash.ai/@h/types/property-type/email/\": All([In(Parameter { parameter: Text(\"https://hash.ai/@h/types/entity-type/user/\") }, Path { path: TypeBaseUrls }), NotEqual(Path { path: Uuid }, ActorId)])}, embedding_exclusions: {\"https://hash.ai/@h/types/entity-type/user/\": [\"https://hash.ai/@h/types/property-type/email/\"]} }" expression: "snapshot_with_params(&expr.transpile_to_string(), &fixture.parameters)" --- -(CASE WHEN ($1 = ANY("entity_is_of_type_ids_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) THEN ARRAY[$3]::text[] ELSE ARRAY[]::text[] END) +(CASE WHEN ($1 = ANY("entity_edition_cache_0_0_1"."base_urls")) AND ("entity_temporal_metadata_0_0_0"."entity_uuid" != $2) THEN ARRAY[$3]::text[] ELSE ARRAY[]::text[] END) parameters: AuxiliaryParameters { initial_offset: 0, parameters: ["https://hash.ai/@h/types/entity-type/user/", ActorEntityUuid(EntityUuid(aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa)), "https://hash.ai/@h/types/property-type/email/"] } From 5a0812f37462e3bb554d1270b35b6b808237556b Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:38:30 +0200 Subject: [PATCH 33/34] chore: fix imports --- tests/graph/integration/postgres/email_filter_protection.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/graph/integration/postgres/email_filter_protection.rs b/tests/graph/integration/postgres/email_filter_protection.rs index 8a5dbd65494..b455a22795a 100644 --- a/tests/graph/integration/postgres/email_filter_protection.rs +++ b/tests/graph/integration/postgres/email_filter_protection.rs @@ -26,7 +26,6 @@ use hash_graph_store::{ CountEntitiesParams, CreateEntityParams, EntityQueryPath, EntityQuerySorting, EntityQuerySortingRecord, EntityStore as _, QueryEntitiesParams, QueryEntitySubgraphParams, }, - entity_type::EntityTypeQueryPath, filter::{ Filter, FilterExpression, JsonPath, Parameter, PathToken, protection::{ @@ -36,10 +35,7 @@ use hash_graph_store::{ }, query::{NullOrdering, Ordering}, subgraph::{ - edges::{ - EdgeDirection, EntityTraversalEdge, EntityTraversalPath, GraphResolveDepths, - SharedEdgeKind, - }, + edges::{EdgeDirection, EntityTraversalEdge, EntityTraversalPath, GraphResolveDepths}, temporal_axes::{ PinnedTemporalAxisUnresolved, QueryTemporalAxesUnresolved, VariableTemporalAxisUnresolved, From d74c496d263425d37d715e79549fe7862590bdf5 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:02:37 +0200 Subject: [PATCH 34/34] fix: comment --- libs/@local/graph/store/src/filter/protection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/@local/graph/store/src/filter/protection.rs b/libs/@local/graph/store/src/filter/protection.rs index c79b4e2900b..22a56f1a519 100644 --- a/libs/@local/graph/store/src/filter/protection.rs +++ b/libs/@local/graph/store/src/filter/protection.rs @@ -568,7 +568,7 @@ use crate::{ pub enum PropertyFilterEntityQueryPath { /// The entity's UUID (`entity_temporal_metadata.entity_uuid`). Uuid, - /// The base URLs of the entity's type(s) (`entity_is_of_type_ids.base_urls`). + /// The base URLs of the entity's type(s) (`entity_edition_cache.base_urls`). TypeBaseUrls, }