Skip to content

Commit b4d2e7d

Browse files
authored
Merge pull request #477 from ProjectASAP/feat/runtime-adaptation-sampling-delta
feat: enforce plan-scoped sampling, GOS, delta, and adaptation
2 parents b581e54 + 4ff7891 commit b4d2e7d

15 files changed

Lines changed: 1665 additions & 125 deletions

File tree

‎control_plane/src/backend_client.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ impl BackendClient {
370370
backend_plan: Vec<u8>,
371371
query_plan: &crate::query_plan::QueryPlan,
372372
storage_routing: Option<serde_json::Value>,
373+
adaptation_evidence: &[crate::physical::compiler::RuntimeAdaptationEvidence],
373374
) -> std::result::Result<(), BackendPostError> {
374375
let url = derive_physical_plan_url(&self.endpoint);
375376
let response = self
@@ -381,6 +382,7 @@ impl BackendClient {
381382
"backend_plan": backend_plan,
382383
"query_plan": query_plan,
383384
"storage_routing": storage_routing,
385+
"adaptation_evidence": adaptation_evidence,
384386
}))
385387
.send()
386388
.await

‎control_plane/src/emit/backend_push.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ async fn push_documents_coupled(
255255
let transmission_plan = match crate::physical::compiler::TransmissionPlan::build(
256256
precompute_plan.envelope.clone(),
257257
precompute_plan,
258+
&Default::default(),
258259
) {
259260
Ok(plan) => plan,
260261
Err(error) => {
@@ -271,6 +272,7 @@ async fn push_documents_coupled(
271272
plan_bytes.clone(),
272273
&query_plan,
273274
Some(routing.clone()),
275+
&[],
274276
)
275277
.await
276278
{

‎control_plane/src/main.rs‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,8 @@ struct PhysicalPlanQueryRequest {
583583
accuracy: types_v2::AccuracyTarget,
584584
lifecycle: physical::compiler::LifecyclePlanningInput,
585585
window_implementations: Vec<physical::compiler::WindowImplementationCandidate>,
586+
#[serde(default)]
587+
runtime_policy: physical::compiler::RuntimeRulePolicy,
586588
}
587589

588590
#[derive(Debug, Deserialize)]
@@ -593,6 +595,8 @@ struct CompileAndPublishPhysicalPlanRequest {
593595
capability_snapshot_id: String,
594596
#[serde(default)]
595597
evidence: HashMap<String, physical::compiler::TopKMembershipEvidence>,
598+
#[serde(default)]
599+
runtime_adaptation_evidence: Vec<physical::compiler::RuntimeAdaptationEvidence>,
596600
planner_revision: String,
597601
max_evidence_age_ms: u64,
598602
plan_version: u64,
@@ -624,10 +628,11 @@ async fn handle_compile_and_publish_physical_plan(
624628
State(st): State<AppState>,
625629
Json(request): Json<CompileAndPublishPhysicalPlanRequest>,
626630
) -> impl IntoResponse {
627-
let (bundle, collector_ids, apply_timeout) = match compile_physical_plan_request(request) {
628-
Ok(compiled) => compiled,
629-
Err(response) => return response.into_response(),
630-
};
631+
let (bundle, collector_ids, apply_timeout, adaptation_evidence) =
632+
match compile_physical_plan_request(request) {
633+
Ok(compiled) => compiled,
634+
Err(response) => return response.into_response(),
635+
};
631636

632637
let Some(backend) = st.backend_client.as_ref() else {
633638
return (
@@ -654,6 +659,7 @@ async fn handle_compile_and_publish_physical_plan(
654659
bundle.backend_plan.encode_to_vec(),
655660
&bundle.query_plan,
656661
None,
662+
&adaptation_evidence,
657663
)
658664
.await
659665
{
@@ -714,7 +720,15 @@ async fn handle_compile_and_publish_physical_plan(
714720
// Only the Send-safe compiled bundle crosses an await point.
715721
fn compile_physical_plan_request(
716722
request: CompileAndPublishPhysicalPlanRequest,
717-
) -> Result<(physical::compiler::PhysicalPlan, Vec<String>, Duration), (StatusCode, String)> {
723+
) -> Result<
724+
(
725+
physical::compiler::PhysicalPlan,
726+
Vec<String>,
727+
Duration,
728+
Vec<physical::compiler::RuntimeAdaptationEvidence>,
729+
),
730+
(StatusCode, String),
731+
> {
718732
if request.queries.is_empty() || request.collector_ids.is_empty() {
719733
return Err((
720734
StatusCode::UNPROCESSABLE_ENTITY,
@@ -780,6 +794,7 @@ fn compile_physical_plan_request(
780794
accuracy: query.accuracy,
781795
lifecycle: query.lifecycle,
782796
window_implementations: query.window_implementations,
797+
runtime_policy: query.runtime_policy,
783798
});
784799
}
785800

@@ -804,7 +819,12 @@ fn compile_physical_plan_request(
804819
Err(error) => return Err((StatusCode::UNPROCESSABLE_ENTITY, error.to_string())),
805820
};
806821
let apply_timeout = Duration::from_millis(request.apply_timeout_ms);
807-
Ok((bundle, request.collector_ids, apply_timeout))
822+
Ok((
823+
bundle,
824+
request.collector_ids,
825+
apply_timeout,
826+
request.runtime_adaptation_evidence,
827+
))
808828
}
809829

810830
// ── Handlers ──────────────────────────────────────────────────────────────────

‎control_plane/src/opamp/mod.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub const PLAN_STATUS_MESSAGE: &str = "plan_status";
5454
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5555
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
5656
pub enum CollectorPlanStatusKind {
57+
Staged,
5758
Applied,
5859
Failed,
5960
}
@@ -283,8 +284,8 @@ impl OpampServer {
283284
}
284285

285286
/// Publish all per-target physical plans and require an exact semantic
286-
/// APPLIED report from every Collector. Config hashes are deliberately not
287-
/// accepted as plan activation evidence.
287+
/// STAGED report from every Collector. Activation is synchronized later at
288+
/// the envelope timestamp; config hashes are not accepted as evidence.
288289
pub async fn publish_collector_plans(
289290
&self,
290291
plans: &[crate::physical::compiler::CollectorPlan],
@@ -327,7 +328,7 @@ impl OpampServer {
327328
plan_id: plan.envelope.plan_id,
328329
plan_version: plan.envelope.plan_version,
329330
})?;
330-
if report.status != CollectorPlanStatusKind::Applied {
331+
if report.status != CollectorPlanStatusKind::Staged {
331332
return Err(CollectorPlanPublishError::Rejected {
332333
collector_id: plan.collector_id.clone(),
333334
plan_id: report.plan_id,
@@ -1044,6 +1045,7 @@ mod tests {
10441045
emit_every_ms: 60_000,
10451046
full_checkpoint_every_ms: None,
10461047
destination_ref: "asapquery-backend".into(),
1048+
runtime_policy: crate::physical::compiler::RuntimeRulePolicy::default(),
10471049
}],
10481050
}
10491051
}
@@ -1065,7 +1067,7 @@ mod tests {
10651067
}
10661068

10671069
#[tokio::test]
1068-
async fn typed_plan_publication_waits_for_capability_and_exact_applied_status() {
1070+
async fn typed_plan_publication_waits_for_capability_and_exact_staged_status() {
10691071
let (srv, addr) = start_server().await;
10701072
let mut agent_ws = connect_ws_client(addr, "edge-a", "agent").await;
10711073

@@ -1105,7 +1107,7 @@ mod tests {
11051107
let status = serde_json::to_vec(&CollectorPlanStatus {
11061108
plan_id: 42,
11071109
plan_version: 1,
1108-
status: CollectorPlanStatusKind::Applied,
1110+
status: CollectorPlanStatusKind::Staged,
11091111
error: None,
11101112
})
11111113
.unwrap();
@@ -1126,7 +1128,7 @@ mod tests {
11261128
assert_eq!(reports.len(), 1);
11271129
assert_eq!(reports[0].plan_id, 42);
11281130
assert_eq!(reports[0].plan_version, 1);
1129-
assert_eq!(reports[0].status, CollectorPlanStatusKind::Applied);
1131+
assert_eq!(reports[0].status, CollectorPlanStatusKind::Staged);
11301132
}
11311133

11321134
#[tokio::test]

0 commit comments

Comments
 (0)