Skip to main content

query_runtime/runtime/context/
admitted.rs

1//! Admitted-plan capability validation for execution contexts.
2
3use std::sync::{Arc, Mutex, PoisonError};
4
5use query_planning::{
6    admission::{AttemptCapabilityEvidence, AttemptProducerOccurrence},
7    error::{QueryError, QueryErrorCode, Result},
8    plan::{QueryPlan, SchemaFingerprint},
9};
10
11use super::{
12    super::{
13        MemoryGrant,
14        resident::{GpuResidentInputMap, GpuResultHandle},
15    },
16    ActivatedDeferredResidentInput, ActivatedDeferredResidentPhase, DeferredResidentFacts, ExecutionContext,
17};
18use crate::observability::AttemptComposedScheduleProjection;
19
20/// The plan identities an admitted attempt may execute: the evidence its
21/// claim submitted, then each fragment that joined its grant after the claim.
22/// Identities are only added; the claim's placement is never revisited. A
23/// placement-only claim starts empty, and its plans fail closed until a
24/// fragment joins.
25#[derive(Debug, Default)]
26pub(in crate::runtime) struct AdmittedPlanIdentities {
27    /// Replaced wholesale on each join. Readers clone the `Arc` and validate
28    /// against one consistent snapshot while a fragment may be joining.
29    evidence: Mutex<Option<Arc<AttemptCapabilityEvidence>>>,
30}
31
32impl AdmittedPlanIdentities {
33    pub(in crate::runtime) fn new(evidence: Option<AttemptCapabilityEvidence>) -> Self {
34        Self { evidence: Mutex::new(evidence.map(Arc::new)) }
35    }
36
37    /// The identities admitted so far.
38    pub(in crate::runtime) fn current(&self) -> Option<Arc<AttemptCapabilityEvidence>> {
39        self.evidence.lock().unwrap_or_else(PoisonError::into_inner).clone()
40    }
41
42    /// Adds a fragment whose capabilities the granted device already
43    /// provides.
44    pub(in crate::runtime) fn join(&self, fragment: &AttemptCapabilityEvidence) -> Result<()> {
45        let mut evidence = self.evidence.lock().unwrap_or_else(PoisonError::into_inner);
46        let mut joined = evidence.as_deref().cloned().unwrap_or_default();
47        joined.join(fragment)?;
48        *evidence = Some(Arc::new(joined));
49        Ok(())
50    }
51}
52
53impl ExecutionContext {
54    /// The admitted evidence and immutable grant a plan validates against, or
55    /// `None` for a standalone context. The grant, allocation domain, and
56    /// attempt identity come from one admission binding, so only the
57    /// evidence can be missing: a placement-only admission carries none
58    /// until a fragment joins it.
59    fn validated_admitted_identity(
60        &self,
61        plan: &QueryPlan,
62    ) -> Result<Option<(Arc<AttemptCapabilityEvidence>, MemoryGrant)>> {
63        let Some(attempt) = self.admitted() else {
64            return Ok(None);
65        };
66        let evidence = attempt.plan_identities.current().ok_or_else(|| {
67            QueryError::new(
68                QueryErrorCode::InternalInvariantViolation,
69                "query_engine.runtime.context",
70                "a query-service context requires its admitted capability evidence",
71            )
72            .with_fact("reason", "admitted_capability_evidence_missing")
73            .with_fact("plan_scope", plan.execution_fingerprint())
74        })?;
75        let immutable_grant = self.admitted_memory_grant(attempt, "query_engine.runtime.context.memory_grant")?;
76        Ok(Some((evidence, immutable_grant)))
77    }
78
79    fn validate_admitted_capability_evidence(
80        &self,
81        evidence: &AttemptCapabilityEvidence,
82        plan: &QueryPlan,
83    ) -> Result<()> {
84        evidence.validate_covers_plan(plan)?;
85        let execution_scope = plan.execution_fingerprint();
86        let expected_schema = SchemaFingerprint::of(plan.caller_visible_output_schema());
87        let scope_matches = evidence.execution_scopes().iter().filter(|shape| shape.matches(plan)).collect::<Vec<_>>();
88        let matching_shapes = scope_matches
89            .iter()
90            .copied()
91            .filter(|shape| shape.output_schema_fingerprint() == expected_schema)
92            .collect::<Vec<_>>();
93        if matching_shapes.is_empty() {
94            // A plan admitted only as a producer occurrence or a manifest
95            // occurrence has no execution-scope shape of its own; its
96            // identity was fixed when that occurrence was composed, so it
97            // passes on the occurrence alone.
98            if evidence.producer_occurrence_scope_count_for_plan(plan) > 0 {
99                return Ok(());
100            }
101            if evidence.execution_manifest().and_then(|manifest| manifest.occurrence_for_plan(plan)).is_some() {
102                return Ok(());
103            }
104            return Err(QueryError::new(
105                QueryErrorCode::InternalInvariantViolation,
106                "query_engine.runtime.context",
107                "the admitted capability evidence output schema differs from the native plan",
108            )
109            .with_fact("reason", "admitted_capability_schema_mismatch")
110            .with_fact("plan_scope", execution_scope)
111            .with_fact("matching_shape_count", scope_matches.len()));
112        }
113        for shape in matching_shapes {
114            shape.validate_manifest_core()?;
115        }
116        Ok(())
117    }
118
119    fn validate_admitted_occurrences(
120        &self,
121        evidence: &AttemptCapabilityEvidence,
122        plan: &QueryPlan,
123        occurrence_ids: &[String],
124    ) -> Result<()> {
125        evidence.validate_composed_occurrences(plan, occurrence_ids)
126    }
127
128    fn validate_admitted_evidence(
129        &self,
130        plan: &QueryPlan,
131        occurrence_ids: Option<&[String]>,
132    ) -> Result<Option<AttemptComposedScheduleProjection>> {
133        let Some((submitted_evidence, immutable_grant)) = self.validated_admitted_identity(plan)? else {
134            return Ok(None);
135        };
136        // An activated deferred-resident phase for this exact plan scope
137        // replaces the submitted evidence with the scope proven at
138        // activation (`with_admitted_gpu_inputs`); every other plan validates
139        // against the evidence submitted at admission.
140        let evidence = match &self.plan.activated_deferred_resident_phase {
141            Some(activated) if activated.consumer_execution_scope_fingerprint == plan.execution_fingerprint() => {
142                if activated.grant_identity != immutable_grant.identity() {
143                    return Err(QueryError::new(
144                        QueryErrorCode::InternalInvariantViolation,
145                        "query_engine.runtime.context",
146                        "the activated deferred phase differs from the admitted plan or immutable grant",
147                    )
148                    .with_fact("reason", "deferred_resident_phase_identity_mismatch"));
149                }
150                &activated.capability_evidence
151            },
152            _ => submitted_evidence.as_ref(),
153        };
154        self.validate_admitted_capability_evidence(evidence, plan)?;
155        if let Some(occurrence_ids) = occurrence_ids {
156            self.validate_admitted_occurrences(evidence, plan, occurrence_ids)?;
157        }
158        Ok(None)
159    }
160
161    /// Universal pre-GPU gate for service-created execution contexts. Direct
162    /// contexts remain unrestricted and return success without evidence.
163    pub fn validate_admitted_plan_capabilities(&self, plan: &QueryPlan) -> Result<()> {
164        let Some((evidence, _)) = self.validated_admitted_identity(plan)? else {
165            return Ok(());
166        };
167        self.validate_admitted_capability_evidence(&evidence, plan)
168    }
169
170    /// Activates the deferred-resident phase for `plan` from its exact
171    /// materialized resident inputs.
172    ///
173    /// Expected producer, occurrence, and schema identities come from the
174    /// admitted compiled binding. Runtime frame identities are recorded only
175    /// after those independent facts have matched.
176    pub fn with_admitted_gpu_inputs(&self, plan: &QueryPlan, gpu_inputs: &GpuResidentInputMap) -> Result<Self> {
177        let Some((submitted_evidence, immutable_grant)) = self.validated_admitted_identity(plan)? else {
178            return Ok(self.clone());
179        };
180        if gpu_inputs.is_empty() {
181            return Ok(self.clone());
182        }
183        // A phase activated for this same scope under a different grant is a
184        // admission violation. Otherwise the phase is re-derived from the
185        // frames presented: one execution context can run a plan more than
186        // once, and a later round may carry a different frame.
187        if let Some(activated) = &self.plan.activated_deferred_resident_phase
188            && activated.consumer_execution_scope_fingerprint == plan.execution_fingerprint()
189            && activated.grant_identity != immutable_grant.identity()
190        {
191            return Err(QueryError::new(
192                QueryErrorCode::InternalInvariantViolation,
193                "query_engine.runtime.context",
194                "the activated deferred phase differs from the admitted immutable grant",
195            )
196            .with_fact("reason", "deferred_resident_phase_identity_mismatch"));
197        }
198        submitted_evidence.validate_covers_plan(plan)?;
199        let expected_scopes = submitted_evidence
200            .execution_scopes()
201            .iter()
202            .filter(|scope| scope.matches(plan) && !scope.producer_occurrences().is_empty())
203            .collect::<Vec<_>>();
204        if expected_scopes.is_empty() {
205            return Ok(self.clone());
206        }
207        for scope in &expected_scopes {
208            scope.validate_manifest_core()?;
209        }
210        let expected_scope = expected_scopes
211            .iter()
212            .copied()
213            .find(|scope| {
214                scope.producer_occurrences().iter().all(|expected| {
215                    gpu_inputs.get(expected.occurrence_id()).is_some_and(|input| {
216                        DeferredResidentFacts::read(expected.occurrence_id(), input)
217                            .is_ok_and(|facts| facts_match_occurrence(&facts, input, expected))
218                    })
219                })
220            })
221            .unwrap_or(expected_scopes[0]);
222        let mut capability_evidence = submitted_evidence.as_ref().clone();
223        capability_evidence.attest_cugraph_graph_execution_scope(plan);
224        let inputs = expected_scope
225            .producer_occurrences()
226            .iter()
227            .map(|expected| {
228                let input_name = expected.occurrence_id();
229                let input = gpu_inputs.get(input_name).ok_or_else(|| {
230                    QueryError::new(
231                        QueryErrorCode::InternalInvariantViolation,
232                        "query_engine.runtime.context",
233                        "the admitted deferred resident occurrence is missing",
234                    )
235                    .with_fact("reason", "deferred_resident_input_missing")
236                    .with_fact("input_name", input_name)
237                })?;
238                let facts = DeferredResidentFacts::read(input_name, input)?;
239                if !facts_match_occurrence(&facts, input, expected) {
240                    return Err(QueryError::new(
241                        QueryErrorCode::InternalInvariantViolation,
242                        "query_engine.runtime.context",
243                        "a deferred resident input differs from its admitted producer binding",
244                    )
245                    .with_fact("reason", "deferred_resident_producer_binding_mismatch")
246                    .with_fact("input_name", input_name)
247                    .with_fact("expected_producer_scope", expected.execution_scope_fingerprint())
248                    .with_fact("actual_producer_scope", facts.producer_execution_scope_fingerprint())
249                    .with_fact("expected_schema", expected.output_schema_fingerprint())
250                    .with_fact("actual_schema", SchemaFingerprint::of(facts.schema())));
251                }
252                ActivatedDeferredResidentInput::bind(input_name.to_owned(), facts, input, &immutable_grant)
253            })
254            .collect::<Result<Vec<_>>>()?;
255        let activated = ActivatedDeferredResidentPhase {
256            consumer_execution_scope_fingerprint: plan.execution_fingerprint(),
257            inputs,
258            capability_evidence,
259            grant_identity: immutable_grant.identity(),
260        };
261        activated.validate_inputs(gpu_inputs, &immutable_grant)?;
262        Ok(self.with_plan(|plan| plan.activated_deferred_resident_phase = Some(Arc::new(activated))))
263    }
264
265    #[doc(hidden)]
266    pub fn validate_admitted_plan_capabilities_with_gpu_inputs(
267        &self,
268        plan: &QueryPlan,
269        gpu_inputs: &GpuResidentInputMap,
270    ) -> Result<()> {
271        self.validate_admitted_plan_capabilities(plan)?;
272        let Some(activated) = self
273            .plan
274            .activated_deferred_resident_phase
275            .as_ref()
276            .filter(|activated| activated.consumer_execution_scope_fingerprint == plan.execution_fingerprint())
277        else {
278            return Ok(());
279        };
280        let immutable_grant = self.memory_grant()?.ok_or_else(|| {
281            QueryError::new(
282                QueryErrorCode::InternalInvariantViolation,
283                "query_engine.runtime.context",
284                "the activated deferred phase lost its immutable grant",
285            )
286            .with_fact("reason", "deferred_resident_grant_missing")
287        })?;
288        activated.validate_inputs(gpu_inputs, &immutable_grant)
289    }
290
291    /// Validates one composed attempt against its admitted capability and
292    /// resident-input identity contracts. Physical allocation remains owned
293    /// by the immutable grant and live resident handles.
294    pub fn validate_admitted_composed_attempt(
295        &self,
296        consumer_plan: &QueryPlan,
297        occurrence_ids: &[String],
298    ) -> Result<Option<AttemptComposedScheduleProjection>> {
299        self.validate_admitted_evidence(consumer_plan, Some(occurrence_ids))
300    }
301}
302
303/// Whether an input's facts are those of the producer occurrence the attempt
304/// admitted: written by the admitted producer scope, or by a plan equivalent
305/// to it, with the admitted output schema.
306fn facts_match_occurrence(
307    facts: &DeferredResidentFacts,
308    input: &GpuResultHandle,
309    expected: &AttemptProducerOccurrence,
310) -> bool {
311    (facts.producer_execution_scope_fingerprint() == expected.execution_scope_fingerprint()
312        || input.producer_execution_plan().is_some_and(|plan| expected.matches_execution_plan(plan)))
313        && SchemaFingerprint::of(facts.schema()) == expected.output_schema_fingerprint()
314}